diff --git a/app/build.gradle b/app/build.gradle index 6169ce4..eb976f4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -74,4 +74,6 @@ dependencies { implementation 'com.facebook.android:facebook-login:[5,6)' implementation 'com.facebook.android:facebook-share:[5,6)' + + implementation 'com.squareup.picasso:picasso:2.5.2' } diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/MainActivity.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/MainActivity.kt index 8854a5a..29e4680 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/MainActivity.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/MainActivity.kt @@ -19,6 +19,7 @@ import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithButtonRecycler import com.facebook.AccessToken import com.facebook.login.LoginManager import com.facebook.share.widget.ShareDialog +import io.reactivex.Observable class MainActivity : AppCompatActivity() { @@ -86,8 +87,13 @@ class MainActivity : AppCompatActivity() { previousPostsRecyclerView.apply { layoutManager = LinearLayoutManager(this@MainActivity) - adapter = PostSummaryRecycler(SocialPlatformsManager.getLoggedIn(this@MainActivity) - .map { it to it.getPosts(this@MainActivity).size }) + adapter = PostSummaryRecycler( + Observable.fromIterable(SocialPlatformsManager.getLoggedIn(this@MainActivity)) + .flatMap { social -> + return@flatMap social.getPosts(this@MainActivity) + .map { social to it } + .toObservable() + }) } } diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/NewPostActivity.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/NewPostActivity.kt index 7c4eee2..a32fd3c 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/NewPostActivity.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/NewPostActivity.kt @@ -20,7 +20,6 @@ import kotlinx.android.synthetic.main.content_new_post.* import pl.edu.amu.wmi.socialaggregator.R import pl.edu.amu.wmi.socialaggregator.utils.SocialPlatformsManager import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithToggleRecycler -import java.util.* class NewPostActivity : AppCompatActivity() { @@ -46,33 +45,20 @@ class NewPostActivity : AppCompatActivity() { adapter = availablesRecycler } - Observable.fromIterable(availablesRecycler.chips.entries) - .filter { file != null } - .filter { (_, chip) -> chip.isChecked } - .flatMap { (social, _) -> - val bitmap = BitmapFactory.decodeFile(file!!) -// Toast.makeText(this, "Posted to ${social.getName()}!", Toast.LENGTH_LONG) -// .show() - social.addPost(this, postText.text?.toString() ?: "test", listOf(bitmap)) - } - .take(availablesRecycler.chips.entries.count { (_, chip) -> chip.isChecked }.toLong()) - .subscribe { - finish() - } - RxView.clicks(publishPost) + .filter { file != null } .subscribe { availablesRecycler.chips.entries - .filter { file != null } .filter { (_, chip) -> chip.isChecked } .forEach { (social, _) -> val bitmap = BitmapFactory.decodeFile(file!!) social.addPost(this, postText.text?.toString() ?: "test", listOf(bitmap)) - Toast.makeText(this, "Posted to ${social.getName()}!", Toast.LENGTH_LONG) + .blockingSubscribe() + Toast.makeText(this, "Posted to ${social.getName()}!", Toast.LENGTH_SHORT) .show() } - finish() } + RxView.clicks(imageView2) .subscribe { val getIntent = Intent(Intent.ACTION_GET_CONTENT) diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/PostHistoryActivity.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/PostHistoryActivity.kt index 3b340d4..a09e240 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/PostHistoryActivity.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/PostHistoryActivity.kt @@ -3,12 +3,15 @@ package pl.edu.amu.wmi.socialaggregator.activity import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager -import pl.edu.amu.wmi.socialaggregator.R - +import io.reactivex.Observable import kotlinx.android.synthetic.main.activity_post_history.* import kotlinx.android.synthetic.main.content_post_history.* +import pl.edu.amu.wmi.socialaggregator.R import pl.edu.amu.wmi.socialaggregator.utils.SocialPlatformsManager import pl.edu.amu.wmi.socialaggregator.viewholders.PostDetailsRecycler +import androidx.recyclerview.widget.DividerItemDecoration + + class PostHistoryActivity : AppCompatActivity() { @@ -22,9 +25,16 @@ class PostHistoryActivity : AppCompatActivity() { postHistoryRecycler.apply { layoutManager = LinearLayoutManager(this@PostHistoryActivity) adapter = PostDetailsRecycler( - SocialPlatformsManager.getLoggedIn(this@PostHistoryActivity) - .flatMap { it.getPosts(this@PostHistoryActivity) } - ) + Observable.fromIterable(SocialPlatformsManager.getLoggedIn(this@PostHistoryActivity)) + .flatMap { + it.getPosts(this@PostHistoryActivity) + .toObservable() + }) + addItemDecoration( + DividerItemDecoration( + context, + (layoutManager as LinearLayoutManager).orientation + )) } } diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/Facebook.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/Facebook.kt index fe1b8a7..3683861 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/Facebook.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/Facebook.kt @@ -4,27 +4,29 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.graphics.Bitmap +import android.icu.text.SimpleDateFormat +import android.os.Bundle import android.util.Log -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet -import com.facebook.CallbackManager -import com.facebook.FacebookCallback -import com.facebook.FacebookException +import com.facebook.* import com.facebook.login.LoginResult import com.facebook.login.widget.LoginButton import pl.edu.amu.wmi.socialaggregator.utils.Utils import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithButtonRecycler -import com.facebook.AccessToken import com.facebook.login.LoginManager import com.facebook.share.Sharer import com.facebook.share.model.ShareMediaContent import com.facebook.share.model.SharePhoto import com.facebook.share.widget.ShareDialog import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject +import org.json.JSONArray +import org.json.JSONObject import pl.edu.amu.wmi.socialaggregator.R import java.lang.Exception @@ -78,8 +80,69 @@ class Facebook : SocialPlatform { return publishSubject } - override fun getPosts(context: Context): List { - return emptyList() + override fun getPosts(context: Context): Single> { + return Single.just(AccessToken.getCurrentAccessToken()) + .map { token -> + if (token.isExpired) { + return@map emptyList() + } else { + val request = GraphRequest.newGraphPathRequest( + AccessToken.getCurrentAccessToken(), + "/me/posts") { + Log.i(getName(), it.toString()) + } + val parameters = Bundle() + parameters.putString("fields", "likes.summary(true),created_time,message,attachments{url,unshimmed_url,media,subattachments}") + request.parameters = parameters + val res = request.executeAndWait() + + val data = res.jsonObject["data"] as JSONArray + val size = data.length() + return@map (0 until size).map { + val obj = data[it] as JSONObject + val msg = if(obj.has("message")) obj.getString("message") else "" + val date = if(obj.has("created_time")) obj.getString("created_time") else "" +// SimpleDateFormat("YYYY-MM-DD\'T\'hh:mm:ssZ").parse(date) + Post(this, msg, date, getImages(obj)) + } + } + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + + } + + private fun getImages(obj: JSONObject): List { + val list = emptyList().toMutableList() + if (obj.has("attachments")) { + val attachments = obj.getJSONObject("attachments").getJSONArray("data") + try { + list.addAll((0 until attachments.length()) + .map { attachments[it] as JSONObject } + .flatMap { + val urls = mutableListOf() + if (it.has("media")) { + urls.add(it.getJSONObject("media") + .getJSONObject("image") + .getString("src")) + } + if (it.has("subattachments")) { + val subattachments = it.getJSONObject("subattachments").getJSONArray("data") + urls.addAll((0 until subattachments.length()) + .map { subattachments[it] as JSONObject } + .map { + it.getJSONObject("media") + .getJSONObject("image") + .getString("src") + }) + } + return@flatMap urls + }) + } catch (e: Exception) { + Log.e(getName(), e.toString()) + } + } + return list } override fun handleButtonView(context: Context): (SocialWithButtonRecycler.ViewHolder) -> Unit = diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/FacebookMock.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/FacebookMock.kt index 2419705..6988a1c 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/FacebookMock.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/FacebookMock.kt @@ -3,6 +3,8 @@ package pl.edu.amu.wmi.socialaggregator.socialplatforms import android.content.Context import android.graphics.Bitmap import android.util.Log +import io.reactivex.Single +import io.reactivex.subjects.PublishSubject import pl.edu.amu.wmi.socialaggregator.R import pl.edu.amu.wmi.socialaggregator.utils.InternalStorage import java.io.ByteArrayOutputStream @@ -39,7 +41,7 @@ abstract class FacebookMock : SocialPlatform { return InternalStorage.getFileOrDir(context, "logins/facebook")?.exists() ?: false } - override fun addPost(context: Context, text: String, images: List) { + override fun addPost(context: Context, text: String, images: List): PublishSubject { val postsDir = InternalStorage.getFileOrDir(context, "posts/facebook") if (postsDir != null) { val postDir = File(postsDir, System.currentTimeMillis().toString()) @@ -62,15 +64,21 @@ abstract class FacebookMock : SocialPlatform { } else { Log.e(TAG, "Could not create posts directory") } + return PublishSubject.create() } - override fun getPosts(context: Context): List { + override fun getPosts(context: Context): Single> { val postsDir = InternalStorage.getFileOrDir(context, "posts/facebook") - return postsDir?.listFiles()?.flatMap { + return Single.just(postsDir?.listFiles()?.flatMap { it.listFiles().map { val dt = Date(it.lastModified()) - Post(this, it.readText(), DateFormat.getDateTimeInstance().format(dt)) + Post( + this, + it.readText(), + DateFormat.getDateTimeInstance().format(dt), + emptyList() + ) } - }?.toList() ?: emptyList() + }?.toList() ?: emptyList()) } } \ No newline at end of file diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/Post.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/Post.kt index 99c7d36..4b7e1af 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/Post.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/Post.kt @@ -1,3 +1,8 @@ package pl.edu.amu.wmi.socialaggregator.socialplatforms -data class Post(val social: SocialPlatform, val content: String, val dateTime: String) \ No newline at end of file +data class Post( + val social: SocialPlatform, + val content: String, + val dateTime: String, + val images: List +) \ No newline at end of file diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/SocialPlatform.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/SocialPlatform.kt index 8b627eb..3214dbe 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/SocialPlatform.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/SocialPlatform.kt @@ -3,6 +3,7 @@ package pl.edu.amu.wmi.socialaggregator.socialplatforms import android.content.Context import android.content.Intent import android.graphics.Bitmap +import io.reactivex.Single import io.reactivex.subjects.PublishSubject import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithButtonRecycler @@ -13,7 +14,7 @@ interface SocialPlatform { fun logout(context: Context) fun isLoggedIn(context: Context): Boolean fun addPost(context: Context, text: String, images: List): PublishSubject - fun getPosts(context: Context): List + fun getPosts(context: Context): Single> fun getLogo(): Int fun handleButtonView(context: Context): (SocialWithButtonRecycler.ViewHolder) -> Unit = {} fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {} diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/PostDetailsRecycler.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/PostDetailsRecycler.kt index 527f77f..3dedaf0 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/PostDetailsRecycler.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/PostDetailsRecycler.kt @@ -1,16 +1,30 @@ package pl.edu.amu.wmi.socialaggregator.viewholders import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import android.widget.ImageView +import android.widget.LinearLayout import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.recyclerview.widget.RecyclerView +import com.squareup.picasso.Picasso +import io.reactivex.Observable import pl.edu.amu.wmi.socialaggregator.R import pl.edu.amu.wmi.socialaggregator.socialplatforms.Post class PostDetailsRecycler( - val posts: List) : RecyclerView.Adapter() { + postsObservable: Observable> +) : RecyclerView.Adapter() { + + val posts = emptyList().toMutableList() + + init { + postsObservable.subscribe { + posts.addAll(it) + notifyDataSetChanged() + } + } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val layout = LayoutInflater.from(parent.context) @@ -19,8 +33,9 @@ class PostDetailsRecycler( val dateTime = layout.findViewById(R.id.postDateTimeTextView) val postContent = layout.findViewById(R.id.postContentTextView) val imageView = layout.findViewById(R.id.postSocialImage) + val imagesContainer = layout.findViewById(R.id.imagesContainer) - return ViewHolder(layout, dateTime, postContent, imageView) + return ViewHolder(layout, dateTime, postContent, imageView, imagesContainer) } override fun getItemCount(): Int { @@ -31,14 +46,40 @@ class PostDetailsRecycler( val post = posts[position] holder.dateTime.text = post.dateTime holder.postContent.text = post.content + holder.postContent.visibility = if (post.content.isBlank()) View.GONE else View.VISIBLE + holder.imageView.setImageResource(post.social.getLogo()) + + holder.imagesContainer.apply { + removeAllViews() + post.images.forEach { + val imageView = ImageView(context) + addView(imageView) + + imageView.apply { + (layoutParams as ViewGroup.MarginLayoutParams) + .apply { + marginEnd = 10 + } + } + + Picasso.with(context) + .load(it) + .placeholder(android.R.drawable.ic_menu_gallery) + .resize(250, 250) + .centerCrop() + .into(imageView) + } + } } class ViewHolder( root: ConstraintLayout, val dateTime: TextView, val postContent: TextView, - val imageView: ImageView + val imageView: ImageView, + val imagesContainer: LinearLayout + ) : RecyclerView.ViewHolder(root) } \ No newline at end of file diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/PostSummaryRecycler.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/PostSummaryRecycler.kt index 809aa75..66165a8 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/PostSummaryRecycler.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/PostSummaryRecycler.kt @@ -6,11 +6,23 @@ import android.widget.ImageView import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.recyclerview.widget.RecyclerView +import io.reactivex.Observable import pl.edu.amu.wmi.socialaggregator.R +import pl.edu.amu.wmi.socialaggregator.socialplatforms.Post import pl.edu.amu.wmi.socialaggregator.socialplatforms.SocialPlatform class PostSummaryRecycler( - val socials: List>) : RecyclerView.Adapter() { + socialsObservable: Observable>> +) : RecyclerView.Adapter() { + + init { + socialsObservable.subscribe { + socials.add(it) + notifyDataSetChanged() + } + } + + val socials = emptyList>>().toMutableList() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val layout = LayoutInflater.from(parent.context) @@ -28,7 +40,8 @@ class PostSummaryRecycler( } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val (social, count) = socials[position] + val (social, posts) = socials[position] + val count = posts.size holder.socialName.text = social.getName() holder.postCount.text = count.toString() + if (count > 1) " posts" else " post" holder.imageView.setImageResource(social.getLogo()) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 247ad4f..89679d3 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -29,7 +29,7 @@ + app:layout_constraintTop_toBottomOf="@+id/postDateTimeTextView" + tools:visibility="visible" /> + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d45d709..874afb1 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,6 +1,6 @@ - #845EC2 - #4E8397 - #D5CABD + #512DA8 + #5E35B1 + #4A148C