From be5e23b6a589f7eae26e2a0666bc62cc6f6d0621 Mon Sep 17 00:00:00 2001 From: Shizen39 Date: Thu, 19 Jul 2018 02:42:00 +0200 Subject: [PATCH] Pre-release apk only --- GeoNews/app/src/main/AndroidManifest.xml | 7 +- .../ArticleDetail/ArticleCommentFragment.kt | 113 +++++------ .../ArticleDetail/ArticleDetailActivity.kt | 65 +++---- .../ArticleDetail/adapters/RV_Adapter.kt | 146 +++----------- .../ListArticles/ListArticlesActivity.kt | 55 ++++-- .../ListArticles/adapters/RV_Adapter.kt | 35 ++-- .../Activities/MapActivity/MapsActivity.kt | 181 ++++++++---------- .../Activities/Splash/SplashActivity.kt | 20 +- .../giorgio/geonews/Data_utils/DB/Constant.kt | 3 - .../giorgio/geonews/Data_utils/DB/UsrUtils.kt | 12 +- .../giorgio/geonews/Data_utils/Models.kt | 1 + .../giorgio/geonews/Data_utils/formatDate.kt | 10 +- .../geonews/Networking/CheckNetworking.kt | 9 +- .../geonews/Networking/CommentsUtils.kt | 44 ++--- .../geonews/Networking/FetchArticles.kt | 39 ++-- .../geonews/Networking/FetchComments.kt | 29 +-- .../res/layout/activity_list_articles.xml | 36 ++-- .../src/main/res/layout/fragment_comments.xml | 131 +++++++------ .../release/res/values/google_maps_api.xml | 2 +- 19 files changed, 410 insertions(+), 528 deletions(-) diff --git a/GeoNews/app/src/main/AndroidManifest.xml b/GeoNews/app/src/main/AndroidManifest.xml index 2c8653e..de50c3e 100644 --- a/GeoNews/app/src/main/AndroidManifest.xml +++ b/GeoNews/app/src/main/AndroidManifest.xml @@ -28,6 +28,7 @@ @@ -58,6 +60,7 @@ @@ -67,9 +70,7 @@ android:resource="@xml/searchable" android:value="com.example.giorgio.geonews.Activities.Main.MainActivity" /> - - - + \ No newline at end of file diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ArticleDetail/ArticleCommentFragment.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ArticleDetail/ArticleCommentFragment.kt index fd8d268..46f270c 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ArticleDetail/ArticleCommentFragment.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ArticleDetail/ArticleCommentFragment.kt @@ -3,6 +3,7 @@ package com.example.giorgio.geonews.Activities.ArticleDetail import android.app.Fragment import android.content.Context import android.os.Bundle +import android.support.v4.widget.SwipeRefreshLayout import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.view.LayoutInflater @@ -14,93 +15,93 @@ import android.widget.Button import android.widget.EditText import android.widget.TextView import android.widget.Toast -import com.example.giorgio.geonews.Data_utils.DB.Constant import com.example.giorgio.geonews.Data_utils.DB.getAndroidID import com.example.giorgio.geonews.Data_utils.DB.getColor import com.example.giorgio.geonews.Data_utils.UsrComment -import com.example.giorgio.geonews.Networking.CheckNetworking -import com.example.giorgio.geonews.Networking.CreateComment -import com.example.giorgio.geonews.Networking.RetrieveUsrID -import com.example.giorgio.geonews.Networking.UpdateComment +import com.example.giorgio.geonews.Networking.* import com.example.giorgio.geonews.R import kotlinx.android.synthetic.main.row_comments.* - - /** * Created by giorgio on 03/07/18. + * Fragment attached to ArticleDetailActivity that show latest user comments in a RV, calling fetchComments and using CommentsUtils and UserUtils + * -> Networking.fetchComments() + * -> CommentsUtils.CreateComment() / .UpdateComment() / .DeleteComment() / .RetrieveUsrID() + * (*) this -> fetchComments.onResponse() -> RV_Adapter.RecyclerViewAdapter + * GeoNews */ + class ArticleCommentFragment : Fragment(), View.OnClickListener { - /** - * Init variables - */ - lateinit var commentInput: EditText //Edit text for input comment - lateinit var my_img: TextView - lateinit var c: Constant - lateinit var android_id: String - lateinit var articleUrl: String - var updating= false - lateinit var oldItem: UsrComment + /** Init variables */ + lateinit var commentInput: EditText // Edit text for input comment + lateinit var my_img: TextView // ImgView for user image id near commentInput + lateinit var android_id: String // User android HW id + lateinit var articleUrl: String // url of selected article + var updating= false // boolean that check if a user is typing in commentInput because it's updating or creating a new comment + lateinit var oldItem: UsrComment // old comment for query purpose, in case user has updated the comment + /** OnCreateView func */ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.fragment_comments, container, false) - - val sendButton: Button = view.findViewById(R.id.send_comment) //init send button - commentInput= view.findViewById(R.id.insert_comment) //find edittext - my_img= view.findViewById(R.id.my_image) - //init helpers - c= Constant() - android_id= getAndroidID(this.context).toString() + val view = inflater.inflate(R.layout.fragment_comments, container, false) // inflate fragment + val sendButton: Button = view.findViewById(R.id.send_comment) // init send comment button + commentInput= view.findViewById(R.id.insert_comment) // find editText + my_img= view.findViewById(R.id.my_image) // find usr image + android_id= getAndroidID(this.context).toString() // get android HW id (-> UsrUtils) - //Edittext break the fullscreen ui. Some adjustment + /* Edittext break the fullscreen ui. Some adjustment */ commentInput.setOnClickListener({activity.window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)}) commentInput.onFocusChangeListener = View.OnFocusChangeListener { v, hasFocus -> if (hasFocus) activity.window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)} - sendButton.setOnClickListener(this)//set send button for sending comments + sendButton.setOnClickListener(this) // set send comment button listener for sending comments return view - } - + /** Called after onCreateView */ override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - //Find RV of comments + /* Find comments RV */ val mRecyclerView = view?.findViewById(R.id.RV_comments) as RecyclerView val mLayoutManager = LinearLayoutManager(this.context) - //mLayoutManager.stackFromEnd = true //in order to visualize always the lates comments mRecyclerView.layoutManager = mLayoutManager - } + /* Sets up a SwipeRefreshLayout.OnRefreshListener invoked when the user performs a swipe-to-refresh gesture. */ + val mSwipeRefreshLayout = view.findViewById(R.id.swiperefreshComment) as SwipeRefreshLayout + mSwipeRefreshLayout.setOnRefreshListener({ + Commenting.fetchComments(this.context, articleUrl) // (*) this -> fetchComments.onResponse() -> RV_Adapter.RecyclerViewAdapter + mSwipeRefreshLayout.isRefreshing = false + }) + } /** - * On button send click, do postComment() and hide keyboard + * -> onCreateView.sendButton. + * On button send comment click, do postComment() and hide keyboard */ override fun onClick(v: View?) { - ArticleDetailActivity().hideSystemUI(activity.window.decorView, false) - if(!updating) { //User start tiping new comment - if (!commentInput.text.isBlank()) { //If user has written something - if (CheckNetworking.isNetworkAvailable(this.context)) { //post the comment if there's interne connection - val usrId = getUsrID() //get user id by it's android_id + article url - my_img.text = usrId //set personal userId near of edittext - CreateComment.post(context, commentInput.text.toString(), articleUrl, android_id, usrId) //make a post request + if(!updating) { // User start typing a new comment (not already existing) + if (!commentInput.text.isBlank()) { // If user has written something in editText, crate comment (else do nothing) + if (CheckNetworking.isNetworkAvailable(this.context)) { + val usrId = getUsrID() // get comments user id by fetching on DB -> + my_img.text = usrId + CreateComment.createComment(context, commentInput.text.toString(), + articleUrl, android_id, usrId) //make a createComment request } else Toast.makeText(this.context, "No internet connection. Please check and try again.", Toast.LENGTH_LONG).show() } } - else { //User is updating his comment + else { // User is updating his comment println(commentInput.text.toString()) - println(oldItem) - if (commentInput.text.toString() != oldItem.comment) { //If user has updated the comment - if (CheckNetworking.isNetworkAvailable(this.context)) //post the comment if there's interne connection - UpdateComment.updateComment(this.context, commentInput.text.toString(), oldItem.id, articleUrl) + if (commentInput.text.toString() != oldItem.comment) { // If user has updated the comment in editText, crate comment (else do nothing) + if (CheckNetworking.isNetworkAvailable(this.context)) + UpdateComment.updateComment(this.context, commentInput.text.toString(), + oldItem.id, articleUrl) else Toast.makeText(this.context, "No internet connection. Please check and try again.", Toast.LENGTH_LONG).show() } - updating=false + updating=false // updated. Change state } - commentInput.text.clear() //clear edit text input - //Hide keyboard after send comment + commentInput.text.clear() // clear edit text input + /* Hide keyboard after send comment */ val editV= this.activity.currentFocus if(editV!=null){ val inputManager = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager @@ -111,27 +112,27 @@ class ArticleCommentFragment : Fragment(), View.OnClickListener { /** - * Get user id by it's android_id + article url + * --> onClick.usrId = + * Get comments user id by it's android_id + article url */ private fun getUsrID() : String { - //set background color + /* set user image background color */ val backgroundColor=getColor(android_id, articleUrl) user_image.background.setTint(backgroundColor) //for my_img.background.setTint(backgroundColor) - - //get and set user id + /* get and set comments user id */ var result= RetrieveUsrID.MakeNetworkRequestAsyncTask().execute(articleUrl, android_id).get() - return if(result != "") //Usr has already written + return if(result != "") // Usr has already written another comment result - else{ //Usr has not already written, get last Usr and add 1 + else{ // Usr has not already written result=RetrieveUsrID.MakeNetworkRequestAsyncTask().execute(articleUrl, null).get() - if(result != ""){ //another usr has already written + if(result != ""){ // Another usr has already written -> get last Usr id + 1 (result.toInt()+1).toString() } - else{//usr comment is first comment + else{ // usr comment is first comment -> get 1 "1" } } diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ArticleDetail/ArticleDetailActivity.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ArticleDetail/ArticleDetailActivity.kt index 2707aab..7a23cfd 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ArticleDetail/ArticleDetailActivity.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ArticleDetail/ArticleDetailActivity.kt @@ -16,26 +16,28 @@ import kotlinx.android.synthetic.main.activity_detail_webview.* /** - * Activity that show the opened article with a webview + * Created by giorgio on 02/07/18. + * Activity that shows the selected article with a webView + * ArticleDetailActivity -> ArticleCommentFragment + * ArticleDetailActivity -> Commenting.fetchComments (articleUrl) + * GeoNews */ - class ArticleDetailActivity : AppCompatActivity() { - //fBack button on actionBar... For webview, in order to go back in history + /** change fBack button on actionBar behavior; For webview, in order to go back in history */ override fun onBackPressed() { if (WV_article_detail.canGoBack()) { hideSystemUI(window.decorView,true) WV_article_detail.goBack() - } else { - - // Otherwise defer to system default behavior. + } else { // Otherwise defer to system default behavior. hideSystemUI(window.decorView,true) super.onBackPressed() } } + //Function to handle UI behavior fun hideSystemUI(decorView: View, hasFocus: Boolean) { if (hasFocus) decorView.systemUiVisibility= ( View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or @@ -49,18 +51,15 @@ class ArticleDetailActivity : AppCompatActivity() { ) } - //OnCreate func + /** OnCreate func */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) hideSystemUI(window.decorView,true) setContentView(R.layout.activity_detail_webview) + WV_article_detail.webViewClient = WebViewClient() // prevent opening in default browser - - //prevent opening in default browser - WV_article_detail.webViewClient = WebViewClient() - - //add some settings for webpages + /* add some settings for webpages */ val settings= WV_article_detail.settings if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { settings.safeBrowsingEnabled = false @@ -72,29 +71,22 @@ class ArticleDetailActivity : AppCompatActivity() { settings.setAppCacheEnabled(true) settings.setAppCachePath("") - //get url from intent of RV_Adapter - val articleUrl= intent.getStringExtra(CustomViewHolder.ARTICLE_LINK_KEY) - - //load webview's url - if(CheckNetworking.isNetworkAvailable(this)) + val articleUrl= intent.getStringExtra(CustomViewHolder.ARTICLE_LINK_KEY) // get url from intent of RV_Adapter + if(CheckNetworking.isNetworkAvailable(this)) // load webview's url WV_article_detail.loadUrl(articleUrl) else Toast.makeText(this, "No internet connection. Please check and try again.", Toast.LENGTH_LONG).show() //set actionbar title title= if (articleUrl.contains("http://")) articleUrl.removePrefix("http://") else articleUrl.removePrefix("https://") - //get fragment + /* Get comments fragment */ val ft = fragmentManager.beginTransaction() val frag= fragmentManager.findFragmentById(R.id.F_comments) as (ArticleCommentFragment) - //pass url to comment fragment - frag.articleUrl=articleUrl - //and hide it - ft.hide(frag) + frag.articleUrl=articleUrl // pass Articleurl to comment fragment so it can use it + ft.hide(frag) // and hide it ft.commit() - - //Set the hide/show button for comments - addShowHideListener(R.id.F_Button, frag, articleUrl) + addShowHideListener(R.id.F_Button, frag, articleUrl) // Set the hide/show button for comments } @@ -105,23 +97,20 @@ class ArticleDetailActivity : AppCompatActivity() { val button = findViewById(buttonId) as FloatingActionButton button.setOnClickListener { val ft = fragmentManager.beginTransaction() - ft.setCustomAnimations(android.R.animator.fade_in, - android.R.animator.fade_out) - if (fragment.isHidden) { + ft.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out) + if (fragment.isHidden) { //show comments if(CheckNetworking.isNetworkAvailable(this)) - Commenting.fetchComments(this, articleUrl) //fetch new comments and VIEW them (in fetchComments.onResponse) - else Toast.makeText(this, "No internet connection. Please check and try again.", Toast.LENGTH_LONG).show() - - ft.show(fragment) //show comments - hideSystemUI(window.decorView,false) //show navbar - ft.addToBackStack(null) //for navbar back button to hide fragment - } else { - ft.hide(fragment) //hide comments + Commenting.fetchComments(this, articleUrl) //fetch new comments and VIEW them (in fetchComments.onResponse) + else Toast.makeText(this, "No internet connection. " + + "Please check and try again.", Toast.LENGTH_LONG).show() + ft.show(fragment) + hideSystemUI(window.decorView,false) //show navbar + ft.addToBackStack(null) //for navbar back button to hide fragment + } else { //hide comments + ft.hide(fragment) hideSystemUI(window.decorView,true) //hide navbar } ft.commit() } } - - } diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ArticleDetail/adapters/RV_Adapter.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ArticleDetail/adapters/RV_Adapter.kt index 4756a29..66d1301 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ArticleDetail/adapters/RV_Adapter.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ArticleDetail/adapters/RV_Adapter.kt @@ -20,63 +20,65 @@ import kotlinx.android.synthetic.main.row_comments.view.* * Created by giorgio on 03/07/18. * The adapter creates new items in the form of ViewHolders, * populates the ViewHolders with data, and returns information about the data. + * (* fetchComments.onResponse()) --> onBindViewHolder() -> customViewHolder.bind() -> onItemLongClickListener().onItemClick() + * GeoNews */ class RecyclerViewAdapter(val social: Social, val listener: OnItemLongClickListener): RecyclerView.Adapter() { + /** Interface to handle clicks and passing items on customViewHolde !!!! + * Interface passed in: CustomViewHolder.bind() + * Function implemented in: Commenting.fetchComments().onResponse() + */ interface OnItemLongClickListener { fun onItemClick(item: UsrComment, update: Boolean, view: View?) } - - // Create new views and inflate it + /* Create new views and inflate it */ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder { val inflater = LayoutInflater.from(parent.context) val comment_row= inflater.inflate(R.layout.row_comments, parent, false) return CustomViewHolder(comment_row) } - // Replace the contents of a view, binding the list items to TextView + /* Replace the contents of a view, binding the list items to TextView */ override fun onBindViewHolder(holder: CustomViewHolder, position: Int) { val comment= social.comments[position] holder.view.user_comment.text= comment.comment holder.view.user_image.text= comment.usr holder.view.date.text=formatDate(comment.date) + holder.view.user_image.background.setTint(getColor(comment.android_id, comment.url)) //Get usr_color by comment url and usr_android_id - holder.view.user_image.background.setTint(getColor(comment.android_id, comment.url)) //Get usr_color by comment url and usr_android_id - - - holder.bind(social.comments[position], listener) //bind listener interface with selected comment + holder.bind(social.comments[position], listener) //bind listener interface with selected comment } - //this method is giving the size of the list + /* this method give size of the list */ override fun getItemCount(): Int { return social.comments.count() } /** - * Custom Holder - */ + * Custom Holder to handle popupMenu + * + */ class CustomViewHolder(val view: View) : RecyclerView.ViewHolder(view) { - fun bind(item: UsrComment, listener: OnItemLongClickListener) { //Called in Commenting.FetchComment() + fun bind(item: UsrComment, listener: OnItemLongClickListener) { // !Called in onBindViewHolder(), bind selected item and an itemCliclLIstener view.setOnLongClickListener { - + /* Create a popupMenu */ val popupMenu= PopupMenu(view.context, it) popupMenu.setOnMenuItemClickListener { mItem -> when(mItem.itemId){ - /** update */ - R.id.update -> { - if(item.android_id== getAndroidID(view.context)){//comment was written by usr - listener.onItemClick(item, true, view) //set costumed click listener + R.id.update -> { /** UPDATE */ + if(item.android_id== getAndroidID(view.context)){ // comment was written by usr + listener.onItemClick(item, true, view) // set costumed click listener -> in fetchComments().onResponse() Toast.makeText(view.context,"Comment updated!",Toast.LENGTH_LONG).show() } else Toast.makeText(view.context,"Can't update other users comments",Toast.LENGTH_LONG).show() true } - /** delete */ - R.id.delete -> { - if(item.android_id== getAndroidID(view.context)){//comment was written by usr - listener.onItemClick(item, false, null) //set costumed click listener + R.id.delete -> { /** DELETE */ + if(item.android_id== getAndroidID(view.context)){ //comment was written by usr + listener.onItemClick(item, false, null) //set costumed click listener -> in fetchComments().onResponse() Toast.makeText(view.context,"Comment deleted!",Toast.LENGTH_LONG).show() } else Toast.makeText(view.context,"Can't delete other users comments",Toast.LENGTH_LONG).show() @@ -88,10 +90,9 @@ class RecyclerViewAdapter(val social: Social, val listener: OnItemLongClickListe } } - popupMenu.inflate(R.menu.menu_main) - //In order to show Icons. Meh. + /* In order to show Icons. Meh. */ try { val fieldMPopup = PopupMenu::class.java.getDeclaredField("mPopup") fieldMPopup.isAccessible = true @@ -103,107 +104,10 @@ class RecyclerViewAdapter(val social: Social, val listener: OnItemLongClickListe Toast.makeText(view.context,"Error showing menu icons.",Toast.LENGTH_LONG).show() Log.getStackTraceString(e) } finally { - //Show Menu - popupMenu.show() + popupMenu.show() //Show Menu } - true } } } - - - - -} - - - - - - - -/* - -/** - * A CustomViewHolder is used to cache the view objects in order to save memory. - */ - -class CustomViewHolder(val view: View, var comment: UsrComment? = null): RecyclerView.ViewHolder(view) { - - init { - - - - - } -} - -*/ -/* -class CustomViewHolder(val view: View, var comment: UsrComment? = null): RecyclerView.ViewHolder(view) { - - - init { - view.setOnLongClickListener{ - println("long clicked pos: ${comment!!.comment}") - - val popupMenu= PopupMenu(view.context, it) - popupMenu.setOnMenuItemClickListener { item -> - when(item.itemId){ - R.id.update -> { - Toast.makeText(view.context,"update",Toast.LENGTH_LONG).show() - - true - } - R.id.delete -> { - if(comment!!.android_id== getAndroidID(view.context)){//comment was written by usr - DeleteComment.MakeNetworkRequestAsyncTask().execute(comment!!.url, comment!!.comment, comment!!.android_id).get() - - - //There i don't want to see deleted comment - - Toast.makeText(view.context,"Comment deleted!",Toast.LENGTH_LONG).show() - } - - else Toast.makeText(view.context,"Can't delete other users comments",Toast.LENGTH_LONG).show() - true - } - else -> { - false - } - - } - } - - popupMenu.inflate(R.menu.menu_main) - - //In order to show Icons. :l - try { - val fieldMPopup = PopupMenu::class.java.getDeclaredField("mPopup") - fieldMPopup.isAccessible = true - val mPopup = fieldMPopup.get(popupMenu) - mPopup.javaClass - .getDeclaredMethod("setForceShowIcon", Boolean::class.java) - .invoke(mPopup, true) - } catch (e: Exception){ - Toast.makeText(view.context,"Error showing menu icons.",Toast.LENGTH_LONG).show() - Log.getStackTraceString(e) - } finally { - //Show Menu - popupMenu.show() - } - - true - } - - } - - - -} -*/ - - - - +} \ No newline at end of file diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ListArticles/ListArticlesActivity.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ListArticles/ListArticlesActivity.kt index 0e6087f..e181140 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ListArticles/ListArticlesActivity.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ListArticles/ListArticlesActivity.kt @@ -1,14 +1,13 @@ package com.example.giorgio.geonews.Activities.ListArticles import android.os.Bundle +import android.support.v4.widget.SwipeRefreshLayout import android.support.v7.app.AppCompatActivity import android.support.v7.widget.LinearLayoutManager import android.view.View import android.widget.Toast -import com.example.giorgio.geonews.Activities.ListArticles.adapters.RecyclerViewAdapter import com.example.giorgio.geonews.Activities.MapActivity.MapsActivity.Companion.COUNTRY_KEY import com.example.giorgio.geonews.Activities.MapActivity.MapsActivity.Companion.QUERIES_KEY -import com.example.giorgio.geonews.Data_utils.News import com.example.giorgio.geonews.Networking.CheckNetworking import com.example.giorgio.geonews.Networking.Networking import com.example.giorgio.geonews.R @@ -16,10 +15,13 @@ import kotlinx.android.synthetic.main.activity_list_articles.* import java.text.SimpleDateFormat import java.util.* - - - - +/** + * Created by giorgio on 25/06/18. + * Activity that takes selected country & queries and call fetchArticles + * -> ListArticlesActivity -> Networking.fetchArticles (end-point, queries) + * (*) this -> fetchArticles.onResponse() -> RV_Adapter.RecyclerViewAdapter.onBindViewHolder() + * GeoNews + */ class ListArticlesActivity : AppCompatActivity() { @@ -34,50 +36,61 @@ class ListArticlesActivity : AppCompatActivity() { ) } - //OnCreate func + /** OnCreate func */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_list_articles) onWindowFocusChanged(true) - //Creates a vertical Layout Manager and Access the RecyclerView Adapter - RV_news.layoutManager= LinearLayoutManager(this) - RV_news.adapter= RecyclerViewAdapter(News(emptyList())) + /* Creates a vertical Layout Manager */ + RV_news.layoutManager= LinearLayoutManager(this) // Adapter added by calling fetchArticles.onResponse() - //Get selected country + /* Get intent values from maps activity */ val e=intent.extras val country= e.getString(COUNTRY_KEY) val queries= e.getString(QUERIES_KEY) + title = Locale("", country).displayCountry + if(queries!=null) " - $queries" else " - top news" + + /* Fetch articles and add a new adapter in RV_news */ + fetchArt(country, queries) // (*) this -> fetchArticles.onResponse() -> RV_Adapter.RecyclerViewAdapter - title = Locale("", country).displayCountry + /* Sets up a SwipeRefreshLayout.OnRefreshListener invoked when the user performs a swipe-to-refresh gesture. */ + val mSwipeRefreshLayout = findViewById(R.id.swiperefreshListArticles) + mSwipeRefreshLayout.setOnRefreshListener({ + /* Fetch articles and add a new adapter in RV_news */ + fetchArt(country, queries) // fetchArticles + mSwipeRefreshLayout.isRefreshing = false + }) + } + + /** + * Fetch articles and add a new adapter in RV_news + */ + private fun fetchArt(country: String, queries: String?){ if(CheckNetworking.isNetworkAvailable(this)) - //Fetch articles and add a new adapter in RV_news (in fetchArticles.onBind) if(queries=="" || queries==null) Networking.fetchArticles(this, getString(R.string.headlines), "country=$country&pageSize=100&") else{ - title= "$title - $queries" val df = SimpleDateFormat("yyyy-MM-dd", Locale("", country)) val cal = Calendar.getInstance() - cal.add(Calendar.DATE, -1) //get from yesterday news - val date = df.format(cal.time) - val language= getLang(country) + cal.add(Calendar.DATE, -1) // get from yesterday news + val date = df.format(cal.time) // set date from when to get news + val language= getLang(country) // Get language based on country Networking.fetchArticles(this, getString(R.string.everything), "language=$language&q=+$queries&from=$date&sortBy=publishedAt&sortBy=relevancy&pageSize=50&") } else Toast.makeText(this, "No internet connection. Please check and try again.", Toast.LENGTH_LONG).show() - } - /** * Get language from country, end-point everything works only with language, not country */ - fun getLang(country: String): String? { + private fun getLang(country: String): String? { val all = Locale.getAvailableLocales() for (locale in all) { if (locale.toString() == country) { return locale.language } } - return "en" + return "" } } diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ListArticles/adapters/RV_Adapter.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ListArticles/adapters/RV_Adapter.kt index b95a6d1..30409c3 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ListArticles/adapters/RV_Adapter.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/ListArticles/adapters/RV_Adapter.kt @@ -15,54 +15,51 @@ import kotlinx.android.synthetic.main.row_articles.view.* /** * Created by giorgio on 01/07/18. - * The adapter creates new items in the form of ViewHolders, - * populates the ViewHolders with data, and returns information about the data. + * The adapter creates new items in the form of ViewHolders, populates the ViewHolders with data, and returns information about the data. + * (* fetchArticles.onResponse()) --> onBindViewHolder() -> customViewHolder -> onItemClick() -> ArticleDetailActivity (articleUrl) + * GeoNews */ -class RecyclerViewAdapter(val news: News): RecyclerView.Adapter() { - // Create new views and inflate it +class RecyclerViewAdapter(val news: News): RecyclerView.Adapter() { + /* Create new views and inflate it */ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder { val inflater = LayoutInflater.from(parent.context) val articles_row= inflater.inflate(R.layout.row_articles, parent, false) return CustomViewHolder(articles_row) } - // Replace the contents of a view, binding the list items to TextView + /* Replace the contents of a view, binding the list items to TextView */ override fun onBindViewHolder(holder: CustomViewHolder, position: Int) { val article= news.articles[position] holder.view.title.text= article.title holder.view.source.text= article.source.name holder.view.description.text= article.description - //Set image - if(article.urlToImage=="null" || article.urlToImage=="" || article.urlToImage==null) - Picasso.get().load(R.drawable.article).placeholder(R.drawable.article) - .resize(250,250) + /* Set image using Picasso */ + if(article.urlToImage=="null" || article.urlToImage=="" || article.urlToImage==null) // Img not available -> use stock image + Picasso.get().load(R.drawable.article).resize(250,250) .centerCrop().into(holder.view.urlToImage) - else + else // Img available -> use stock image as placeholder Picasso.get().load(article.urlToImage).placeholder(R.drawable.article_background_placeholder) - .resize(500,250) - .centerCrop().into(holder.view.urlToImage) + .resize(500,250).centerCrop().into(holder.view.urlToImage) - //Set publishedAt in form of "x time ago" - holder.view.publishedAt.text= formatDate(article.publishedAt) + holder.view.publishedAt.text= formatDate(article.publishedAt) // Set publishedAt in form of "x time ago" - //bind article url and holder url - holder.article= article + holder.article= article // bind article url and holder url } - //this method is giving the size of the list + //Get size of the list override fun getItemCount(): Int { return news.articles.count() } } /** - * A CustomViewHolder is used to cache the view objects in order to save memory. + * OnItemClick sends selected article url through intent RV_news.RV_Adapter -> ArticleDetailActivity */ class CustomViewHolder(val view: View, var article: Article?=null): RecyclerView.ViewHolder(view) { companion object { - val ARTICLE_LINK_KEY= "ARTICLE_LINK" + const val ARTICLE_LINK_KEY= "ARTICLE_LINK" } init { view.setOnClickListener { diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/MapActivity/MapsActivity.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/MapActivity/MapsActivity.kt index 0ea6c34..7708ec1 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/MapActivity/MapsActivity.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/MapActivity/MapsActivity.kt @@ -10,7 +10,6 @@ import android.os.Bundle import android.support.annotation.RequiresApi import android.support.v7.app.AppCompatActivity import android.support.v7.widget.SearchView -import android.util.Log import android.view.Menu import android.view.View import android.view.WindowManager @@ -30,26 +29,17 @@ import kotlinx.coroutines.experimental.launch import net.danlew.android.joda.JodaTimeAndroid import java.util.* +/** + * Created by giorgio on 16/06/18. + * Activity that consists of a map (google Map) that display available articles with a marker on that location. + * There's also a search bar (searchView) with which you can search keyword to retrieve relative news + * MapsActivity -> ListArticlesActivity (countryIso, queries?) + * GeoNews + */ class MapsActivity : AppCompatActivity(), GoogleMap.OnMarkerClickListener, OnMapReadyCallback { - - private lateinit var mMap: GoogleMap - lateinit var searchView: SearchView - var queries: String? =null - - - /** INFLATE SEARCH BAR*/ - override fun onCreateOptionsMenu(menu: Menu): Boolean { - // Inflate the options menu from XML - val inflater = menuInflater - inflater.inflate(R.menu.menu_search, menu) - - return true - } - //Set FullScreen override fun onWindowFocusChanged(hasFocus: Boolean) { - Log.w(this.toString(), hasFocus.toString()) super.onWindowFocusChanged(hasFocus) if (hasFocus) window.decorView.systemUiVisibility= (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY //fullscreen mode or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // Set the content to appear under the system bars @@ -59,39 +49,53 @@ class MapsActivity : AppCompatActivity(), GoogleMap.OnMarkerClickListener, OnMap ) } + /** Declare variables */ + private lateinit var mMap: GoogleMap + lateinit var searchView: SearchView + var queries: String? =null // Eventual queries of searchView that will be passed to LisArticleActivity's Intent + var blue=false // If a query was typed, than all maps markers will turn to Blue. Set blue=true if so. used + private lateinit var countriesISO: Array // List of countries in ISO format + + /** inflate searchView menus */ + override fun onCreateOptionsMenu(menu: Menu): Boolean { + // Inflate the options menu from XML (menu->menu_search) + val inflater = menuInflater + inflater.inflate(R.menu.menu_search, menu) + return true + } + + /** Object that conteins key values for intent communications */ companion object { //country to send to articledetailactivity val COUNTRY_KEY="COUNTRY" val QUERIES_KEY="QUERIES" } - - var blue=false - //OnCreate func + /** OnCreare activity */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - JodaTimeAndroid.init(this) //init date-time lib + JodaTimeAndroid.init(this) // init date-time lib setContentView(R.layout.activity_maps) - onWindowFocusChanged(true) - // Obtain the SupportMapFragment and get notified when the map is ready to be used. - val mapFragment = supportFragmentManager + + val mapFragment = supportFragmentManager // Obtain the SupportMapFragment and get notified when the map is ready to be used. .findFragmentById(R.id.map) as SupportMapFragment mapFragment.getMapAsync(this) - /**SearchBar stuffs*/ + /* SearchBar stuffs */ searchView = findViewById(R.id.SV_search) - searchView.setOnClickListener { - /*handle keyboard showing up*/ + searchView.setOnClickListener { /* handle keyboard showing up */ window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) searchView.isIconified = false } + /* OnQueryListener */ searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { onWindowFocusChanged(true) - queries= query - Toast.makeText(baseContext, "Ricerca news di $queries, seleziona un marker.", Toast.LENGTH_LONG).show() - if(!blue) fetch(true) + queries= query // Assign just written query to queries variable, in order to pass it in the Intent + Toast.makeText(baseContext, + "Select a geo-merker to view \"$queries\" news.", Toast.LENGTH_LONG).show() + if(!blue) fetchMarkers(true) searchView.queryHint=query searchView.isIconified = true searchView.clearFocus() @@ -101,9 +105,9 @@ class MapsActivity : AppCompatActivity(), GoogleMap.OnMarkerClickListener, OnMap return false } }) - + /* Hide keyboard */ searchView.setOnCloseListener { - if(blue) fetch(false) + if(blue) fetchMarkers(false) searchView.queryHint="Top news" queries="" val inputManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager @@ -114,22 +118,16 @@ class MapsActivity : AppCompatActivity(), GoogleMap.OnMarkerClickListener, OnMap } } - - - private lateinit var countriesISO: ArrayList //list of countries in ISO format - /** - * Called when the map is ready + * Called when the Map is ready */ @RequiresApi(Build.VERSION_CODES.N) override fun onMapReady(googleMap: GoogleMap) { + /** Customise the styling of the base map using a JSON object defined in a raw resource file. */ try { - // Customise the styling of the base map using a JSON object defined - // in a raw resource file. val success = googleMap.setMapStyle( MapStyleOptions.loadRawResourceStyle( this, R.raw.style_json)) - if (!success) { println("Style parsing failed.") } @@ -137,111 +135,102 @@ class MapsActivity : AppCompatActivity(), GoogleMap.OnMarkerClickListener, OnMap println("Can't find style. Error: ") } + /**Init lateinit variables */ mMap = googleMap mMap.setMaxZoomPreference(4.7F) mMap.isIndoorEnabled = false mMap.isBuildingsEnabled = false mMap.isTrafficEnabled = false + countriesISO= arrayOf("ae", "ar", "at", "au", "be" ,"bg", "br", "ca", "ch", "cn", "co", "cu", "cz", "de", "eg", "fr", "gb", "gr","hk", "hu", "id", "ie" ,"il" ,"in", "it", "jp", "kr", "lt", "lv", "ma", "mx", "my", "ng", "nl", "no", "nz" ,"ph", "pl", "pt", "ro", "rs", "ru", "sa", "se", "sg", "si", "sk", "th", "tr", "tw", "ua", "us" ,"ve", "za") - countriesISO= arrayListOf("ae", "ar", "at", "au", "be" ,"bg", "br", "ca", "ch", "cn", "co", "cu", "cz", "de", "eg", "fr", "gb", "gr","hk", "hu", "id", "ie" ,"il" ,"in", "it", "jp", "kr", "lt", "lv", "ma", "mx", "my", "ng", "nl", "no", "nz" ,"ph", "pl", "pt", "ro", "rs", "ru", "sa", "se", "sg", "si", "sk", "th", "tr", "tw", "ua", "us" ,"ve", "za") - - fetch(false) - - mMap.setOnMarkerClickListener(this) //click listener on map's markers + /** Fetch markers */ + fetchMarkers(false) } /** * Fetch markers */ - - private fun fetch(boolean: Boolean){ - blue=boolean + private fun fetchMarkers(isBlue: Boolean){ + blue=isBlue if(CheckNetworking.isNetworkAvailable(baseContext)) for(i in countriesISO.indices) { - val deferred= async(context=CommonPool){ //deferred==future in java... val that eventually will have a value - getLatLng(i) + val deferred= async(context=CommonPool){ // deferred==future in java... val that eventually will have a value + getLatLng(i) // get address obj from countryIso } - launch (context = UI){ //on the main ui context, it will not block the main thread thanks await() that suspend thread and resume it when deferred will have a result - setMarker(deferred.await(), boolean) + + launch (context = UI){ // on the main ui context, it will not block the main thread thanks await() + setMarker(deferred.await(), isBlue) // hat suspend thread and resume it when deferred will have a result } - deferred.invokeOnCompletion { + deferred.invokeOnCompletion { // free coroutine on completion deferred.cancel() } - }else Toast.makeText(baseContext, "No internet connection. Check and try again.", Toast.LENGTH_LONG).show() + } else Toast.makeText(baseContext, "No internet connection. Check and try again.", Toast.LENGTH_LONG).show() - mMap.setOnMarkerClickListener(this@MapsActivity) + mMap.setOnMarkerClickListener(this) // click listener on map's markers. Override -> } /** - * retrieve Address object from country name, that contains latitude and longitude (and others stuff) + * Retrieve Address object from country name, that contains latitude and longitude (and others stuff) */ - fun getLatLng(i:Int): Address { //retrieve Address object from country name, that contains latitude and longitude (and others stuff) - val geocoder = Geocoder(this) //used to retrieve position from location name - val address= Locale("", countriesISO[i]).displayCountry - - println("Getting $address position....") - - return geocoder.getFromLocationName(address, 1)[0] + private fun getLatLng(i:Int): Address { // for getting latitude and longitude + val geocoder = Geocoder(this) // used to retrieve position from location name + val address= Locale("", countriesISO[i]).displayCountry // get country Name from countryIso + return geocoder.getFromLocationName(address, 1)[0] // from country Name return LatLong } /** - * set map markers + * Insert map markers */ - fun setMarker(address: Address, search: Boolean){ + private fun setMarker(address: Address, search: Boolean){ if(search) mMap.addMarker(MarkerOptions() .position(LatLng(address.latitude,address.longitude)) .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)) - .title(address.countryCode)).tag = 0 //set the name of the marker + .title(address.countryCode)).tag = 0 // set the name of the marker else mMap.addMarker(MarkerOptions() - .position(LatLng(address.latitude,address.longitude)) //Set marker position (by lat and long) - .title(address.countryCode)).tag = 0 //set the name of the marker - - } - + .position(LatLng(address.latitude,address.longitude)) // Set marker position (by lat and long) + .title(address.countryCode)).tag = 0 // set name of the marker and tag = 0 that will count how many times a marker get clicks + } // note: tag is one per marker - - /** Called when the user clicks a marker. */ + /** -> fetchMarkers.mMap. + * Called when the user clicks a marker. + * */ override fun onMarkerClick(marker:Marker):Boolean { onWindowFocusChanged(true) - // Retrieve the data from the marker. - var clickCount = marker.tag as Int? + + var clickCount = marker.tag as Int? // Retrieve the data from the marker's tag marker.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE)) - // Check if a click count was set - if (clickCount != null){ - clickCount += 1 //clicked - marker.tag = clickCount //bind with marker data - Toast.makeText(this, "Click another time to view articles of " + marker.title, Toast.LENGTH_SHORT).show() - //was already clicked, clicked another time - if(clickCount==2){ - //Send intent whit selected country to articleDetailActivity - val intent= Intent(this, ListArticlesActivity::class.java) + if (clickCount != null){ // Check if a click count was set + clickCount += 1 // clicked one time + marker.tag = clickCount // bind with marker data + Toast.makeText(this, + "Click another time to view articles of " + + Locale("", marker.title).displayCountry, + Toast.LENGTH_SHORT).show() + + if(clickCount==2){ //was already clicked, clicked another time + val intent= Intent(this, ListArticlesActivity::class.java) //Send intent with selected country to articleDetailActivity val e=Bundle() e.putString(QUERIES_KEY, queries) e.putString(COUNTRY_KEY, marker.title.toLowerCase()) - intent.putExtras(e) //send country iso code and eventually queries to fetchnews's query + intent.putExtras(e) //send country iso code and eventually the queries to fetchnews's query this.startActivity(intent) - clickCount -= 1 //clicked - marker.tag = clickCount //bind with marker data - //marker.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE)) - + clickCount -= 1 //restore clicked status + marker.tag = clickCount //bind with marker data } - //Clicked the map - mMap.setOnMapClickListener { + mMap.setOnMapClickListener { //Clicked the map, not a marker onWindowFocusChanged(true) clickCount -= 1 marker.tag = clickCount - if(blue) marker.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE)) else marker.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)) + if(blue) marker.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE)) + else marker.setIcon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)) } } - // Return false to indicate that we have not consumed the event and that we wish - // for the default behavior to occur (which is for the camera to move such that the - // marker is centered and for the marker's info window to open, if it has one). - + // Return false: we have not consumed the event and we wish for the default behavior to occur return false } diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/Splash/SplashActivity.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/Splash/SplashActivity.kt index 9a85358..1339326 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/Splash/SplashActivity.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Activities/Splash/SplashActivity.kt @@ -6,29 +6,25 @@ import android.support.v7.app.AppCompatActivity import android.view.View import com.example.giorgio.geonews.Activities.MapActivity.MapsActivity -class SplashActivity : AppCompatActivity() { - - //Set FullScreen - - - +/** + * Created by giorgio on 15/06/18. + * Simple splash activity. Uses @style/SplashTheme + * GeoNews + */ +class SplashActivity : AppCompatActivity() { + //Set fullscreen override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) window.decorView.systemUiVisibility= (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY //fullscreen mode or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // Set the content to appear under the system bars or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN) // Hide the nav bar and status bar - else window.decorView.systemUiVisibility=( - View.SYSTEM_UI_FLAG_FULLSCREEN - ) } - //OnCreate func override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - onWindowFocusChanged(true) - + //Launch map activity val intent= Intent(this, MapsActivity::class.java) this.startActivity(intent) this.finish() diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/DB/Constant.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/DB/Constant.kt index f1bafcc..7defcde 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/DB/Constant.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/DB/Constant.kt @@ -15,7 +15,4 @@ class Constant { val READ_MAX_USR= BASE_PATH + "getMaxUsr.php" val READ_USR= BASE_PATH + "getUsr.php" - - - } \ No newline at end of file diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/DB/UsrUtils.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/DB/UsrUtils.kt index 95b7a09..023c21f 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/DB/UsrUtils.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/DB/UsrUtils.kt @@ -8,16 +8,18 @@ import android.provider.Settings /** * Created by giorgio on 10/07/18. + * Usr local utilities + * GeoNews */ - +/** Get android HW id */ @SuppressLint("HardwareIds") fun getAndroidID(context: Context): String? { return Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) } - +/** Get personal color from getAndroidID() and articleUrl */ fun getColor(android_id: String, url:String): Int { var seed:String = Regex("[^a-z0-9]").replace(url, "") seed = if(seed.length<=70) //otherwise cannot find color @@ -37,6 +39,7 @@ fun getColor(android_id: String, url:String): Int { } } +/** Generate pseudo random colors from hashcode */ private fun intToARGB(i: Int): String { return Integer.toHexString(i shr 16 and 0xFF) + Integer.toHexString(i shr 8 and 0xFF) + @@ -44,9 +47,4 @@ private fun intToARGB(i: Int): String { } -/* - - -*/ - diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/Models.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/Models.kt index 14c8b89..bebb232 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/Models.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/Models.kt @@ -4,6 +4,7 @@ package com.example.giorgio.geonews.Data_utils * Created by giorgio on 01/07/18. * Model; * Custom data class that represents an article from a news source. + * GeoNews */ class News(val articles: List
) diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/formatDate.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/formatDate.kt index 8e34af8..5e30ab5 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/formatDate.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Data_utils/formatDate.kt @@ -6,6 +6,8 @@ import org.joda.time.format.DateTimeFormat /** * Created by giorgio on 02/07/18. + * Date formatter + * GeoNews */ object formatter{ @@ -23,9 +25,9 @@ object formatter{ // Uses one of DateUtils' static methods to compare how long ago the article was published. // Time spans in the past are formatted like "42 minutes ago". Time spans in the future are formatted like "In 42 minutes". val relativeTime = DateUtils.getRelativeTimeSpanString( - millisecondsSinceUnixEpoch,//article time - System.currentTimeMillis(),//now - MINUTE_IN_MILLIS) // Minimum time to be displayed (secs == "0min ago") + millisecondsSinceUnixEpoch, //article time + System.currentTimeMillis(), //now + MINUTE_IN_MILLIS) // Minimum time to be displayed (secs == "0min ago") // Initially converts relativeTime to a String to possibly set the following TextView as "just now!". Otherwise, sets the publish time as is. val relativeTimeString = relativeTime.toString() @@ -45,8 +47,6 @@ object formatter{ * two chars of seconds in order to satisfy DateTimeFormat's format. * * takes utcDateTime is the UTC date-time according to ISO 8601 standards. - * - * throws IndexOutOfBoundsException since some custom indexing will be out of bounds of the # of time-attribute parts. */ @Throws(IndexOutOfBoundsException::class) private fun formatUTCDateTime(utcDateTime: String?): String { diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/CheckNetworking.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/CheckNetworking.kt index 2ba0531..34bd77a 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/CheckNetworking.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/CheckNetworking.kt @@ -4,15 +4,14 @@ import android.content.Context import android.net.ConnectivityManager /** - * Created by giorgio on 12/07/18. + * Created by giorgio on 03/07/18. + * Extension object for checking internet connection + * GeoNews */ object CheckNetworking { - - - fun isNetworkAvailable(context: Context): Boolean { +fun isNetworkAvailable(context: Context): Boolean { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val activeNetworkInfo = connectivityManager.activeNetworkInfo return activeNetworkInfo != null && activeNetworkInfo.isConnected } - } \ No newline at end of file diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/CommentsUtils.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/CommentsUtils.kt index 78aa03c..43c46aa 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/CommentsUtils.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/CommentsUtils.kt @@ -14,15 +14,14 @@ import java.io.IOException /** * Created by giorgio on 10/07/18. * CRUD STUFFS + * Uses Data_utils.DB.Constants for get php url */ - - /** * @CREATE a new comment, posting it on the DB */ object CreateComment{ - fun post(context: Context, commentInput:String, articleUrl: String, android_id: String, usrId:String){ + fun createComment(context: Context, commentInput:String, articleUrl: String, android_id: String, usrId:String){ val formBody= FormBody.Builder() .add("comment", commentInput) .add("url", articleUrl) @@ -35,16 +34,12 @@ object CreateComment{ .post(formBody) .build() - client.newCall(request).enqueue(object : Callback { //can't .execute() on the main thread! + client.newCall(request).enqueue(object : Callback { //can't .execute() on the main thread! override fun onFailure(call: Call?, e: IOException?) { (context as Activity).runOnUiThread { Toast.makeText(context, "Failed to fetch data. Retry later.", Toast.LENGTH_LONG).show() } } - /** - * GSON: parse json body response's fields, binding them whit Models - */ override fun onResponse(call: Call?, response: Response?) { - //Read all comments - Commenting.fetchComments(context, articleUrl) + Commenting.fetchComments(context, articleUrl) //Read all comments } }) } @@ -55,25 +50,24 @@ object CreateComment{ * @READ all users id of actual article comments, and return the user id of the user that have posted a comment */ object RetrieveUsrID { - //Fetch all usrID SYNCHRONOUSLY + /* Fetch all usrID SYNCHRONOUSLY */ fun getUsrID(articleUrl: String, android_id: String?): String { val client = OkHttpClient() - val url = if (android_id != null) //Usr has already written, get his usrid - Constant().READ_USR + "?url=" + "\"" + articleUrl + "\"" + "&android_id=" + "\"" + android_id + "\"" //usr_id or null - else //Usr has not already written, get last usrid - Constant().READ_MAX_USR + "?url=" + "\"" + articleUrl + "\"" //usr_id + val url = if (android_id != null) { // Usr has already written, get his usrid + Constant().READ_USR + "?url=" + "\"" + articleUrl + "\"" + + "&android_id=" + "\"" + android_id + "\"" // usr_id or null + } else // Usr has not already written, get last usrid + Constant().READ_MAX_USR + "?url=" + "\"" + articleUrl + "\"" // usr_id val req = Request.Builder().url(url).build() - client.newCall(req).execute().use { response -> //do it synchronously because of id needed + client.newCall(req).execute().use { response -> // do it synchronously because of id needed if (!response.isSuccessful) throw IOException("Unexpected code $response") - val body = response?.body()?.string() //json body response - println(body) //TODO: WARNING, BODY IF NULL CHANGES WITH " \N" + val body = response?.body()?.string() // json body response return if (!body.equals("{\"Usr\":[]} ") && !body.equals("{\"Usr\":[{\"usr\":null}]} \n")) { - //Bind models and json fields val gson = GsonBuilder().create() - val usr = gson.fromJson(body, UsrID::class.java) //from json to java obj + val usr = gson.fromJson(body, UsrID::class.java) // Bind models and json fields usr.Usr[0].usr } else "" } @@ -95,10 +89,8 @@ object RetrieveUsrID { } - object DeleteComment{ fun deleteComment(context: Activity, articleUrl: String, id: String) { - val formBody= FormBody.Builder() .add("url", articleUrl) .add("id", id) @@ -110,13 +102,12 @@ object DeleteComment{ .post(formBody) .build() - client.newCall(request).enqueue(object : Callback { //can't .execute() on the main thread! + client.newCall(request).enqueue(object : Callback { // can't .execute() on the main thread! override fun onFailure(call: Call?, e: IOException?) { context.runOnUiThread { Toast.makeText(context, "Failed to fetch data. Retry later.", Toast.LENGTH_LONG).show() } } override fun onResponse(call: Call?, response: Response?) { - //Read all comments - Commenting.fetchComments(context, articleUrl) + Commenting.fetchComments(context, articleUrl) // Read all comments } }) } @@ -135,13 +126,12 @@ object UpdateComment{ .post(formBody) .build() - client.newCall(request).enqueue(object : Callback { //can't .execute() on the main thread! + client.newCall(request).enqueue(object : Callback { // can't .execute() on the main thread! override fun onFailure(call: Call?, e: IOException?) { (context as Activity).runOnUiThread { Toast.makeText(context, "Failed to fetch data. Retry later.", Toast.LENGTH_LONG).show() } } override fun onResponse(call: Call?, response: Response?) { - //Read all comments - Commenting.fetchComments(context, articleUrl) + Commenting.fetchComments(context, articleUrl) //Read all comments } }) } diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/FetchArticles.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/FetchArticles.kt index 358855f..c724b57 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/FetchArticles.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/FetchArticles.kt @@ -1,7 +1,6 @@ package com.example.giorgio.geonews.Networking import android.app.Activity -import android.util.Log import android.widget.Toast import com.example.giorgio.geonews.Activities.ListArticles.adapters.RecyclerViewAdapter import com.example.giorgio.geonews.Data_utils.News @@ -14,43 +13,37 @@ import java.io.IOException /** * Created by giorgio on 02/07/18. - * Singleton class that consists of helper methods used for requesting and retrieving - * article data from the News API + * Singleton class that consists of helper methods used for requesting and retrieving article data from the News API + * (*) fetchArticles.onResponse() -> ListArticle.RV_Adapter - setOnItemClickListener(Article) (ListArticle.adapters.RV_Adapter) + * GeoNews */ object Networking { - /** - * OKHTTP: Queries the articles' dataset and returns a list of Article objects. + * OKHTTP: Queries the articles' dataset and returns a list of Article parsed objects, + * whit which it will add the adapter do Articles RecyclerView + * GSON: parse json body response's fields, binding them whit Models */ fun fetchArticles(context: Activity, end_point: String, queries: String?) { - val URL = getUrl(context, end_point, queries) - // Performs HTTP request (GET) and return a JSON response. + val URL = getUrl(context, end_point, queries) //Function to form the final URL val client = OkHttpClient() val request = Request.Builder().url(URL).build() - Log.w("URL", URL) - client.newCall(request).enqueue(object : Callback { //can't .execute() on the main thread! + client.newCall(request).enqueue(object : Callback { //can't .execute() on the main thread! + override fun onResponse(call: Call?, response: Response?) { + val body = response?.body()?.string() //json body response + val gson = GsonBuilder().create() + + val news = gson.fromJson(body, News::class.java) //Bind models and json fields + context.runOnUiThread {context.RV_news.adapter= RecyclerViewAdapter(news) } //Send obj to the adapter in a background thread + } + override fun onFailure(call: Call?, e: IOException?) { println("Failed to execute request (okhttp)") context.runOnUiThread { Toast.makeText(context, "Failed to fetch data. Retry later.", Toast.LENGTH_LONG).show() } } - - /** - * GSON: parse json body response's fields, binding them whit Models - */ - override fun onResponse(call: Call?, response: Response?) { - val body = response?.body()?.string() //json body response - - //Bind models and json fields - val gson = GsonBuilder().create() - val news = gson.fromJson(body, News::class.java) //from json to java obj - - //Send obj to the adapter in a background thread - context.runOnUiThread {context.RV_news.adapter= RecyclerViewAdapter(news) } - } }) } diff --git a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/FetchComments.kt b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/FetchComments.kt index e469ac9..1b0ac90 100644 --- a/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/FetchComments.kt +++ b/GeoNews/app/src/main/java/com/example/giorgio/geonews/Networking/FetchComments.kt @@ -21,6 +21,8 @@ import java.io.IOException /** * Created by giorgio on 09/07/18. + * Singleton class that consists of helper methods used for requesting and retrieving comments data from geonews.altervista.org + * (*) fetchComments.onResponse() -> ArticleDetail.RV_Adatper - onBindViewHolder().bind() -> (Interface) OnItemLongClickListener -> OnItemClick -> popupMenu */ object Commenting{ @@ -32,37 +34,38 @@ object Commenting{ val client = OkHttpClient() val req = Request.Builder().url(url).build() - client.newCall(req).enqueue(object : Callback { // cannot use .execute() in the UI thread + client.newCall(req).enqueue(object : Callback { // cannot use .execute() in the UI thread override fun onResponse(call: Call?, response: Response?) { - val body = response?.body()?.string() //json body response + val body = response?.body()?.string() // json body response val social:Social - if (!body.equals("{\"comments\":[]}")) { - //Bind models and json fields + if (!body.equals("{\"comments\":[]}")) { // if list of comments isn't empty val gson = GsonBuilder().create() - social = gson.fromJson(body, Social::class.java) //from json to java obj + social = gson.fromJson(body, Social::class.java) // Bind models and json fields } - else{ //Comment placeholder + else{ // Comment placeholder (nothing to show) val comment= listOf(UsrComment("0","Nothing to show", "http://www.nope.it", "nope", " ", " ")) social= Social(comment) } - //Send obj to the adapter in a background thread - (context as Activity).runOnUiThread { //= RecyclerViewAdapter(social) + /* Send obj to the adapter in a background thread */ + (context as Activity).runOnUiThread { /** Implementation of OnItemLongClickListener.onItemCLick() */ context.RV_comments.adapter = RecyclerViewAdapter(social, object : RecyclerViewAdapter.OnItemLongClickListener { + /** (*) Implements onItemClicks function of RV_Adapter.OnItemLongClickListener().OnItemClick() */ override fun onItemClick(item: UsrComment, update: Boolean, view: View?) { - if(update) { + if(update) { // Usr popupMenu is Update val frag= context.fragmentManager.findFragmentById(R.id.F_comments) as (ArticleCommentFragment) frag.commentInput.setText(item.comment) - //request edittext focus and show keyboard + /* request editText focus and show keyboard */ frag.commentInput.requestFocus() val imm = frag.view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.showSoftInput(frag.commentInput, InputMethodManager.SHOW_IMPLICIT) - //make flag updating true so the user can modify the comment in the textview (updateComment ->commentFragment.sendB.onCLick()) + /* make flag updating true so the user can modify the comment in the textview + (updateComment ->commentFragment.sendB.onCLick()) */ frag.updating=true frag.oldItem= item - }else{ + }else{ // Usr popupMenu is Delete if(CheckNetworking.isNetworkAvailable(context)) - DeleteComment.deleteComment(context, item.url, item.id)//MakeNetworkRequestAsyncTask().execute(item.url, item.comment, item.android_id).get() + DeleteComment.deleteComment(context, item.url, item.id) else Toast.makeText(context, "No internet connection. Please check and try again.", Toast.LENGTH_LONG).show() } } diff --git a/GeoNews/app/src/main/res/layout/activity_list_articles.xml b/GeoNews/app/src/main/res/layout/activity_list_articles.xml index 05f009c..0b50755 100644 --- a/GeoNews/app/src/main/res/layout/activity_list_articles.xml +++ b/GeoNews/app/src/main/res/layout/activity_list_articles.xml @@ -1,19 +1,25 @@ - + android:layout_height="match_parent"> + - - + + + diff --git a/GeoNews/app/src/main/res/layout/fragment_comments.xml b/GeoNews/app/src/main/res/layout/fragment_comments.xml index e1c20fa..ffd27dc 100644 --- a/GeoNews/app/src/main/res/layout/fragment_comments.xml +++ b/GeoNews/app/src/main/res/layout/fragment_comments.xml @@ -1,73 +1,78 @@ - + android:layout_height="match_parent"> + + - + - +