Merge with master

This commit is contained in:
mjaillot 2020-05-05 20:05:29 +02:00
commit e1a1e88e65
62 changed files with 1496 additions and 399 deletions

View File

@ -1,4 +1,5 @@
# PixelDroid
Software Development Project course, EPFL, Spring 2020
![Pixeldroid project logo](pixeldroid_logo.png)
Free (as in freedom) Android client for Pixelfed, the federated image sharing platform.
[![Build Status](https://gitlab.com/Matttter/PixelDroid/badges/master/pipeline.svg)](https://gitlab.com/Matttter/PixelDroid/pipelines) [![Maintainability](https://api.codeclimate.com/v1/badges/a4f1747dc60b96eb74df/maintainability)](https://codeclimate.com/github/H-PixelDroid/PixelDroid/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/a4f1747dc60b96eb74df/test_coverage)](https://codeclimate.com/github/H-PixelDroid/PixelDroid/test_coverage)
[![Build Status](https://gitlab.com/Matttter/PixelDroid/badges/master/pipeline.svg)](https://gitlab.com/Matttter/PixelDroid/pipelines) [![Maintainability](https://api.codeclimate.com/v1/badges/a4f1747dc60b96eb74df/maintainability)](https://codeclimate.com/github/H-PixelDroid/PixelDroid/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/a4f1747dc60b96eb74df/test_coverage)](https://codeclimate.com/github/H-PixelDroid/PixelDroid/test_coverage) [![Translation status](https://weblate.pixeldroid.org/widgets/pixeldroid/-/pixeldroid/svg-badge.svg)](https://weblate.pixeldroid.org/engage/pixeldroid/?utm_source=widget)

View File

@ -105,7 +105,6 @@ dependencies {
def fragment_version = '1.2.4'
debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
}
tasks.withType(Test) {

View File

@ -76,6 +76,17 @@ class MockedServerTest {
Thread.sleep(3000)
onView(first(withId(R.id.tag_name))).check(matches(withText("#caturday")))
}
@Test
fun openDiscoverPost(){
activityScenario.onActivity{
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(1)?.select()
}
Thread.sleep(1000)
onView(withId(R.id.discoverList)).perform(click())
Thread.sleep(1000)
onView(withId(R.id.username)).check(matches(withText("machintuck")))
}
@Test
@ -201,18 +212,23 @@ class MockedServerTest {
Thread.sleep(10000)
onView(withText("Dante")).check(matches(withId(R.id.accountNameTextView)))
}
/*
@Test
fun swipingLeftStopsAtProfile() {
onView(withId(R.id.main_activity_main_linear_layout))
.perform(ViewActions.swipeLeft()) // search
.perform(ViewActions.swipeLeft()) // camera
.perform(ViewActions.swipeLeft()) // notifications
.perform(ViewActions.swipeLeft()) // profile
.perform(ViewActions.swipeLeft()) // should stop at profile
onView(withId(R.id.nbFollowersTextView)).check(matches(isDisplayed()))
fun clickNotificationRePost() {
ActivityScenario.launch(MainActivity::class.java).onActivity{
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(3)?.select()
}
*/
Thread.sleep(1000)
onView(withId(R.id.view_pager)).perform(ViewActions.swipeUp()).perform(ViewActions.swipeDown())
Thread.sleep(1000)
onView(withText("Clement shared your post")).perform(ViewActions.click())
Thread.sleep(1000)
onView(first(withText("Clement"))).check(matches(withId(R.id.username)))
}
@Test
fun swipingRightStopsAtHomepage() {
activityScenario.onActivity {
@ -229,6 +245,38 @@ class MockedServerTest {
onView(withId(R.id.list)).check(matches(isDisplayed()))
}
@Test
fun swipingLeftStopsAtPublicTimeline() {
activityScenario.onActivity {
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(0)?.select()
}
Thread.sleep(1000)
onView(withId(R.id.main_activity_main_linear_layout))
.perform(ViewActions.swipeLeft()) // notifications
.perform(ViewActions.swipeLeft()) // camera
.perform(ViewActions.swipeLeft()) // search
.perform(ViewActions.swipeLeft()) // homepage
.perform(ViewActions.swipeLeft()) // should stop at homepage
onView(withId(R.id.list)).check(matches(isDisplayed()))
}
@Test
fun swipingPublicTimelineWorks() {
activityScenario.onActivity {
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(4)?.select()
} // go to the last tab
Thread.sleep(1000)
onView(withId(R.id.main_activity_main_linear_layout))
.perform(ViewActions.swipeUp()) // notifications
.perform(ViewActions.swipeUp()) // camera
.perform(ViewActions.swipeUp()) // search
.perform(ViewActions.swipeUp()) // homepage
.perform(ViewActions.swipeUp()) // should stop at homepage
onView(withId(R.id.list)).check(matches(isDisplayed()))
}
@Test
fun clickingTabOnAlbumShowsNextPhoto() {
ActivityScenario.launch(MainActivity::class.java).onActivity {

View File

@ -0,0 +1,174 @@
package com.h.pixeldroid
import android.content.Context
import android.content.Intent
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.longClick
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.RootMatchers
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import com.h.pixeldroid.objects.Account
import com.h.pixeldroid.objects.Attachment
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.testUtility.MockServer
import org.hamcrest.CoreMatchers
import org.hamcrest.Matcher
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.Timeout
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PostTest {
private lateinit var context: Context
@get:Rule
var globalTimeout: Timeout = Timeout.seconds(100)
@Before
fun before(){
context = InstrumentationRegistry.getInstrumentation().targetContext
val mockServer = MockServer()
mockServer.start()
val baseUrl = mockServer.getUrl()
val preferences = context.getSharedPreferences(
"com.h.pixeldroid.pref",
Context.MODE_PRIVATE)
preferences.edit().putString("accessToken", "azerty").apply()
preferences.edit().putString("domain", baseUrl.toString()).apply()
Intents.init()
}
@Test
fun saveToGalleryTestSimplePost() {
val attachment = Attachment(
id = "12",
url = "https://wiki.gnugen.ch/lib/tpl/gnugen/images/logo_web.png"
)
val post = Status(
id = "12",
account = Account(
id = "12",
username = "douze",
url = "https://pixelfed.de/douze"
),
media_attachments = listOf(attachment)
)
val intent = Intent(context, PostActivity::class.java)
intent.putExtra(Status.POST_TAG, post)
ActivityScenario.launch<PostActivity>(intent)
onView(withId(R.id.postPicture)).perform(longClick())
onView(withText(R.string.save_to_gallery)).inRoot(RootMatchers.isPlatformPopup()).perform(click())
Thread.sleep(300)
onView(withText(R.string.image_download_downloading)).inRoot(
RootMatchers.hasWindowLayoutParams()
).check(matches(isDisplayed()))
Thread.sleep(5000)
}
@Test
fun saveToGalleryTestAlbum() {
val attachment1 = Attachment(
id = "12",
url = "https://wiki.gnugen.ch/lib/tpl/gnugen/images/logo_web.png"
)
val attachment2 = Attachment(
id = "13",
url = "https://wiki.gnugen.ch/lib/tpl/gnugen/images/logo_web.png"
)
val post = Status(
id = "12",
account = Account(
id = "12",
username = "douze",
url = "https://pixelfed.de/douze"
),
media_attachments = listOf(attachment1, attachment2)
)
val intent = Intent(context, PostActivity::class.java)
intent.putExtra(Status.POST_TAG, post)
ActivityScenario.launch<PostActivity>(intent)
onView(withId(R.id.imageImageView)).perform(longClick())
onView(withText(R.string.save_to_gallery)).inRoot(RootMatchers.isPlatformPopup()).perform(click())
Thread.sleep(300)
onView(withText(R.string.image_download_downloading)).inRoot(
RootMatchers.hasWindowLayoutParams()
).check(matches(isDisplayed()))
Thread.sleep(5000)
}
@Test
fun shareTestSimplePost() {
val expectedIntent: Matcher<Intent> = IntentMatchers.hasAction(Intent.ACTION_CHOOSER)
val attachment = Attachment(
id = "12",
url = "https://wiki.gnugen.ch/lib/tpl/gnugen/images/logo_web.png"
)
val post = Status(
id = "12",
account = Account(
id = "12",
username = "douze",
url = "https://pixelfed.de/douze"
),
media_attachments = listOf(attachment)
)
val intent = Intent(context, PostActivity::class.java)
intent.putExtra(Status.POST_TAG, post)
ActivityScenario.launch<PostActivity>(intent)
onView(withId(R.id.postPicture)).perform(longClick())
onView(withText(R.string.share_picture)).inRoot(RootMatchers.isPlatformPopup()).perform(click())
Thread.sleep(2000)
Intents.intended(expectedIntent)
}
@Test
fun shareIntentAlbumTest() {
val expectedIntent: Matcher<Intent> = IntentMatchers.hasAction(Intent.ACTION_CHOOSER)
val attachment1 = Attachment(
id = "12",
url = "https://wiki.gnugen.ch/lib/tpl/gnugen/images/logo_web.png"
)
val attachment2 = Attachment(
id = "13",
url = "https://wiki.gnugen.ch/lib/tpl/gnugen/images/logo_web.png"
)
val post = Status(
id = "12",
account = Account(
id = "12",
username = "douze",
url = "https://pixelfed.de/douze"
),
media_attachments = listOf(attachment1, attachment2)
)
val intent = Intent(context, PostActivity::class.java)
intent.putExtra(Status.POST_TAG, post)
ActivityScenario.launch<PostActivity>(intent)
onView(withId(R.id.imageImageView)).perform(longClick())
onView(withText(R.string.share_picture)).inRoot(RootMatchers.isPlatformPopup()).perform(click())
Thread.sleep(2000)
Intents.intended(expectedIntent)
}
@After
fun after() {
Intents.release()
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.h.pixeldroid">
<uses-permission android:name="android.permission.INTERNET" />
@ -19,20 +20,32 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".PostCreationActivity" />
<activity android:name=".FollowsActivity" />
<activity android:name=".PostActivity" />
<activity android:name=".ProfileActivity" />
<activity android:name=".PostCreationActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity" />
<activity android:name=".FollowsActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity"/>
<activity android:name=".PostActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity"/>
<activity android:name=".ProfileActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity"/>
<activity
android:name=".SettingsActivity"
android:label="@string/title_activity_settings2">
android:label="@string/title_activity_settings2"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
<activity
android:name=".MainActivity"
android:theme="@style/AppTheme.Launcher">
android:theme="@style/AppTheme.Launcher"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -41,7 +54,9 @@
<activity
android:name=".LoginActivity"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustResize">
android:windowSoftInputMode="adjustResize"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -55,9 +70,13 @@
</activity>
<activity
android:name=".SearchActivity"
android:launchMode="singleTop">
android:launchMode="singleTop"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<action android:name="android.intent.action.SEARCH"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity"/>
</intent-filter>
<meta-data
android:name="android.app.searchable"

View File

@ -8,10 +8,13 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.PopupMenu
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
import com.h.pixeldroid.utils.ImageConverter
import com.h.pixeldroid.utils.ImageUtils
import kotlinx.android.synthetic.main.post_fragment.view.*
import java.io.Serializable
@ -38,9 +41,30 @@ class ImageFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_image, container, false)
view.findViewById<ImageView>(R.id.imageImageView).setOnLongClickListener {
PopupMenu(view.context, it).apply {
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.image_popup_menu_save_to_gallery -> {
ImageUtils.downloadImage(requireActivity(), view.context, imgUrl)
true
}
R.id.image_popup_menu_share_picture -> {
ImageUtils.downloadImage(requireActivity(), view.context, imgUrl, share = true)
true
}
else -> false
}
}
inflate(R.menu.image_popup_menu)
show()
}
true
}
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_image, container, false)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -52,6 +76,7 @@ class ImageFragment : Fragment() {
.placeholder(ColorDrawable(Color.GRAY))
picRequest.load(imgUrl).into(imageView)
}
companion object {

View File

@ -24,6 +24,7 @@ import com.h.pixeldroid.fragments.NewPostFragment
import com.h.pixeldroid.fragments.SearchDiscoverFragment
import com.h.pixeldroid.fragments.feeds.PostsFeedFragment
import com.h.pixeldroid.fragments.feeds.NotificationsFragment
import com.h.pixeldroid.fragments.feeds.PublicTimelineFragment
import com.h.pixeldroid.objects.Account
import com.h.pixeldroid.utils.ImageConverter
import retrofit2.Call
@ -53,7 +54,16 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
launchActivity(LoginActivity())
} else {
setupDrawer()
setupTabs()
val tabs = arrayOf(
PostsFeedFragment(),
searchDiscoverFragment,
NewPostFragment(),
NotificationsFragment(),
PublicTimelineFragment()
)
setupTabs(tabs)
}
}
@ -88,19 +98,13 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
}
override fun onFailure(call: Call<Account>, t: Throwable) {
Log.e("ProfileActivity:", t.toString())
Log.e("DRAWER ACCOUNT:", t.toString())
}
})
}
private fun setupTabs(){
val tabs = arrayOf(
PostsFeedFragment(),
searchDiscoverFragment,
NewPostFragment(),
NotificationsFragment(),
Fragment()
)
private fun setupTabs(tabs: Array<Fragment>){
viewPager = findViewById(R.id.view_pager)
viewPager.adapter = object : FragmentStateAdapter(this) {
@ -119,7 +123,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
1 -> tab.icon = getDrawable(R.drawable.ic_search_white_24dp)
2 -> tab.icon = getDrawable(R.drawable.ic_photo_camera_white_24dp)
3 -> tab.icon = getDrawable(R.drawable.ic_heart)
4 -> tab.icon = getDrawable(R.drawable.ic_person_white_24dp)
4 -> tab.icon = getDrawable(R.drawable.ic_filter_black_24dp)
}
}.attach()
}

View File

@ -1,28 +1,80 @@
package com.h.pixeldroid
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.fragments.PostFragment
import com.h.pixeldroid.objects.DiscoverPost
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.objects.Status.Companion.DISCOVER_TAG
import com.h.pixeldroid.objects.Status.Companion.DOMAIN_TAG
import com.h.pixeldroid.objects.Status.Companion.POST_TAG
import kotlinx.android.synthetic.main.activity_post.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class PostActivity : AppCompatActivity() {
private lateinit var preferences: SharedPreferences
lateinit var postFragment : PostFragment
lateinit var domain : String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_post)
val status = intent.getSerializableExtra(POST_TAG) as Status?
val discoverPost: DiscoverPost? = intent.getSerializableExtra(DISCOVER_TAG) as DiscoverPost?
preferences = getSharedPreferences(
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
)
domain = preferences.getString("domain", "")!!
postFragment = PostFragment()
val arguments = Bundle()
arguments.putString(DOMAIN_TAG, domain)
if (discoverPost != null) {
postProgressBar.visibility = View.VISIBLE
getDiscoverPost(arguments, discoverPost)
} else {
initializeFragment(arguments, status)
}
}
private fun getDiscoverPost(
arguments: Bundle,
discoverPost: DiscoverPost
) {
val api = PixelfedAPI.create(domain)
val accessToken = preferences.getString("accessToken", "") ?: ""
val id = discoverPost.url?.substringAfterLast('/') ?: ""
api.getStatus("Bearer $accessToken", id).enqueue(object : Callback<Status> {
override fun onFailure(call: Call<Status>, t: Throwable) {
Log.e("PostActivity:", t.toString())
}
override fun onResponse(call: Call<Status>, response: Response<Status>) {
if(response.code() == 200) {
val status = response.body()!!
postProgressBar.visibility = View.GONE
initializeFragment(arguments, status)
}
}
})
}
private fun initializeFragment(arguments: Bundle, status: Status?){
arguments.putSerializable(POST_TAG, status)
postFragment.arguments = arguments
supportFragmentManager.beginTransaction()
.add(R.id.postFragmentSingle, postFragment).commit()
postFragmentSingle.visibility = View.VISIBLE
}
}

View File

@ -21,7 +21,6 @@ import com.google.android.material.tabs.TabLayoutMediator
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.fragments.ProfileBookmarkFragment
import com.h.pixeldroid.fragments.ProfilePostFragment
import com.h.pixeldroid.fragments.ProfileTabsFragment
import com.h.pixeldroid.objects.Account
import com.h.pixeldroid.objects.Account.Companion.ACCOUNT_TAG
import com.h.pixeldroid.objects.Relationship
@ -52,7 +51,13 @@ class ProfileActivity : AppCompatActivity() {
// Set profile according to given account
account = intent.getSerializableExtra(ACCOUNT_TAG) as Account?
if (account == null) {
account?.let {
setViews()
activateFollow()
val tabs = arrayOf(ProfilePostFragment(), Fragment())
setTabs(tabs)
} ?: run {
pixelfedAPI.verifyCredentials("Bearer $accessToken")
.enqueue(object : Callback<Account> {
override fun onResponse(call: Call<Account>, response: Response<Account>) {
@ -73,14 +78,7 @@ class ProfileActivity : AppCompatActivity() {
// Edit button redirects to Pixelfed's "edit account" page
val editButton = findViewById<Button>(R.id.editButton)
editButton.visibility = View.VISIBLE
editButton.setOnClickListener { onClickEditButton() }
} else {
setViews()
activateFollow()
val tabs = arrayOf(ProfilePostFragment(), Fragment())
setTabs(tabs)
editButton.setOnClickListener{ onClickEditButton() }
}
}

View File

@ -18,6 +18,17 @@ import retrofit2.http.Field
interface PixelfedAPI {
companion object {
fun create(baseUrl: String): PixelfedAPI {
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(PixelfedAPI::class.java)
}
}
@FormUrlEncoded
@POST("/api/v1/apps")
fun registerApplication(
@ -231,15 +242,11 @@ interface PixelfedAPI {
@Path("id") accountId : String
): Call<Account>
companion object {
fun create(baseUrl: String): PixelfedAPI {
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(PixelfedAPI::class.java)
}
}
@GET("/api/v1/statuses/{id}")
fun getStatus(
@Header("Authorization") authorization: String,
@Path("id") accountId : String
): Call<Status>
@Multipart
@POST("/api/v1/media")
@ -252,5 +259,11 @@ interface PixelfedAPI {
// get instance configuration
@GET("/api/v1/instance")
fun instance() : Call<Instance>
// get discover
@GET("/api/v2/discover/posts")
fun discover(
@Header("Authorization") authorization: String
) : Call<DiscoverPosts>
}

View File

@ -14,6 +14,7 @@ import com.h.pixeldroid.R
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.fragments.feeds.PostViewHolder
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.objects.Status.Companion.DOMAIN_TAG
import com.h.pixeldroid.objects.Status.Companion.POST_TAG
@ -24,12 +25,13 @@ class PostFragment : Fragment() {
savedInstanceState: Bundle?
): View? {
val status = arguments?.getSerializable(POST_TAG) as Status?
val domain = arguments?.getString(DOMAIN_TAG)!!
val root = inflater.inflate(R.layout.post_fragment, container, false)
val picRequest = Glide.with(this)
.asDrawable().fitCenter()
.placeholder(ColorDrawable(Color.GRAY))
status?.setupPost(root, picRequest, this)
status?.setupPost(root, picRequest, this, domain, true)
//Setup arguments needed for the onclicklisteners
val holder = PostViewHolder(root, requireContext())

View File

@ -32,9 +32,9 @@ class ProfilePostsRecyclerViewAdapter(
setSquareImageFromURL(holder.postView, post.getPostPreviewURL(), holder.postPreview)
holder.postPreview.setOnClickListener {
val intent = Intent(context, PostActivity::class.java)
val intent = Intent(holder.postPreview.context, PostActivity::class.java)
intent.putExtra(Status.POST_TAG, post)
context.startActivity(intent)
holder.postPreview.context.startActivity(intent)
}
}

View File

@ -4,25 +4,44 @@ import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.h.pixeldroid.BuildConfig
import com.h.pixeldroid.PostActivity
import com.h.pixeldroid.R
import com.h.pixeldroid.SearchActivity
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.objects.DiscoverPost
import com.h.pixeldroid.objects.DiscoverPosts
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.utils.ImageConverter
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
/**
* This fragment lets you search and use PixelFed's Discover feature
* This fragment lets you search and use Pixelfed's Discover feature
*/
class SearchDiscoverFragment : Fragment() {
lateinit var api: PixelfedAPI
private lateinit var api: PixelfedAPI
private lateinit var preferences: SharedPreferences
private lateinit var recycler : RecyclerView
private lateinit var adapter : DiscoverRecyclerViewAdapter
private lateinit var accessToken: String
private lateinit var discoverProgressBar: ProgressBar
private lateinit var discoverRefreshLayout: SwipeRefreshLayout
override fun onCreateView(
@ -37,6 +56,11 @@ class SearchDiscoverFragment : Fragment() {
intent.putExtra("searchFeed", search.text.toString())
startActivity(intent)
}
// Set posts RecyclerView as a grid with 3 columns
recycler = view.findViewById(R.id.discoverList)
recycler.layoutManager = GridLayoutManager(requireContext(), 3)
adapter = DiscoverRecyclerViewAdapter()
recycler.adapter = adapter
return view
}
@ -49,5 +73,67 @@ class SearchDiscoverFragment : Fragment() {
api = PixelfedAPI.create("${preferences.getString("domain", "")}")
accessToken = preferences.getString("accessToken", "") ?: ""
discoverProgressBar = view.findViewById(R.id.discoverProgressBar)
discoverRefreshLayout = view.findViewById(R.id.discoverRefreshLayout)
getDiscover()
discoverRefreshLayout.setOnRefreshListener {
getDiscover()
}
}
private fun getDiscover() {
api.discover("Bearer $accessToken")
.enqueue(object : Callback<DiscoverPosts> {
override fun onFailure(call: Call<DiscoverPosts>, t: Throwable) {
Log.e("SearchDiscoverFragment:", t.toString())
}
override fun onResponse(call: Call<DiscoverPosts>, response: Response<DiscoverPosts>) {
if(response.code() == 200) {
val discoverPosts = response.body()!!
adapter.addPosts(discoverPosts.posts)
discoverProgressBar.visibility = View.GONE
discoverRefreshLayout.isRefreshing = false
}
}
})
}
/**
* [RecyclerView.Adapter] that can display a list of [DiscoverPost]s
*/
class DiscoverRecyclerViewAdapter: RecyclerView.Adapter<DiscoverRecyclerViewAdapter.ViewHolder>() {
private val posts: ArrayList<DiscoverPost> = ArrayList()
fun addPosts(newPosts : List<DiscoverPost>) {
posts.clear()
posts.addAll(newPosts)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_profile_posts, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val post = posts[position]
ImageConverter.setSquareImageFromURL(holder.postView, post.thumb, holder.postPreview)
holder.postPreview.setOnClickListener {
val intent = Intent(holder.postView.context, PostActivity::class.java)
intent.putExtra(Status.DISCOVER_TAG, post)
holder.postView.context.startActivity(intent)
}
}
override fun getItemCount(): Int = posts.size
inner class ViewHolder(val postView: View) : RecyclerView.ViewHolder(postView) {
val postPreview: ImageView = postView.findViewById(R.id.postPreview)
}
}
}

View File

@ -20,12 +20,10 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.bumptech.glide.ListPreloader.PreloadModelProvider
import com.h.pixeldroid.BuildConfig
import com.h.pixeldroid.R
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.objects.FeedContent
import com.h.pixeldroid.objects.Status
import kotlinx.android.synthetic.main.fragment_feed.view.*
import retrofit2.Call
import retrofit2.Callback
@ -106,7 +104,7 @@ open class FeedFragment<T: FeedContent, VH: RecyclerView.ViewHolder?>: Fragment(
//do nothing here, it is expected to pull to refresh to load newer notifications
}
private fun enqueueCall(call: Call<List<T>>, callback: LoadCallback<T>){
protected open fun enqueueCall(call: Call<List<T>>, callback: LoadCallback<T>){
call.enqueue(object : Callback<List<T>> {
override fun onResponse(call: Call<List<T>>, response: Response<List<T>>) {

View File

@ -108,16 +108,21 @@ class NotificationsFragment : FeedFragment<Notification, NotificationsFragment.N
openActivity(notification)
}
}
private fun openPostFromNotifcation(notification: Notification) : Intent {
val intent = Intent(context, PostActivity::class.java)
intent.putExtra(Status.POST_TAG, notification.status)
return intent
}
private fun openActivity(notification: Notification){
val intent: Intent
when (notification.type){
Notification.NotificationType.mention, Notification.NotificationType.favourite-> {
intent = Intent(context, PostActivity::class.java)
intent.putExtra(Status.POST_TAG, notification.status)
intent = openPostFromNotifcation(notification)
}
Notification.NotificationType.reblog-> {
Toast.makeText(context,"Can't see shares yet, sorry!", Toast.LENGTH_SHORT).show()
return
intent = openPostFromNotifcation(notification)
}
Notification.NotificationType.follow -> {
intent = Intent(context, ProfileActivity::class.java)

View File

@ -4,6 +4,7 @@ import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -23,17 +24,17 @@ import com.h.pixeldroid.R
import com.h.pixeldroid.objects.Status
import retrofit2.Call
open class PostsFeedFragment : FeedFragment<Status, PostViewHolder>() {
lateinit var picRequest: RequestBuilder<Drawable>
lateinit var domain : String
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)
domain = preferences.getString("domain", "")!!
//RequestBuilder that is re-used for every image
picRequest = Glide.with(this)
.asDrawable().fitCenter()
@ -106,7 +107,7 @@ open class PostsFeedFragment : FeedFragment<Status, PostViewHolder>() {
holder.postPic.maxHeight = metrics.heightPixels
//Setup the post layout
post.setupPost(holder.postView, picRequest, postsFeedFragment)
post.setupPost(holder.postView, picRequest, this@PostsFeedFragment, domain, false)
//Set the special HTML text
post.setDescription(holder.postView, api, credential)
@ -157,4 +158,6 @@ class PostViewHolder(val postView: View, val context: android.content.Context) :
val commentCont : LinearLayout = postView.findViewById(R.id.commentContainer)
val commentIn : LinearLayout = postView.findViewById(R.id.commentIn)
val viewComment : TextView = postView.findViewById(R.id.ViewComments)
val postDate : TextView = postView.findViewById(R.id.postDate)
val postDomain : TextView = postView.findViewById(R.id.postDomain)
}

View File

@ -0,0 +1,45 @@
package com.h.pixeldroid.fragments.feeds
import androidx.lifecycle.LiveData
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import com.h.pixeldroid.objects.Status
import retrofit2.Call
class PublicTimelineFragment: PostsFeedFragment() {
inner class SearchFeedDataSource(
) : FeedDataSource(null, null){
override fun newSource(): FeedDataSource {
return SearchFeedDataSource()
}
private fun makeInitialCall(requestedLoadSize: Int): Call<List<Status>> {
return pixelfedAPI.timelinePublic(limit="$requestedLoadSize")
}
private fun makeAfterCall(requestedLoadSize: Int, key: String): Call<List<Status>> {
return pixelfedAPI.timelinePublic( max_id=key, limit="$requestedLoadSize")
}
override fun loadInitial(
params: LoadInitialParams<String>,
callback: LoadInitialCallback<Status>
) {
enqueueCall(makeInitialCall(params.requestedLoadSize), callback)
}
//This is called to when we get to the bottom of the loaded content, so we want statuses
//older than the given key (params.key)
override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<Status>) {
enqueueCall(makeAfterCall(params.requestedLoadSize, params.key), callback)
}
}
override fun makeContent(): LiveData<PagedList<Status>> {
val config: PagedList.Config = PagedList.Config.Builder().setPageSize(10).build()
factory = FeedFragment<Status, PostViewHolder>()
.FeedDataSourceFactory(SearchFeedDataSource())
return LivePagedListBuilder(factory, config).build()
}
}

View File

@ -55,15 +55,15 @@ class SearchAccountFragment: AccountListFragment(){
params: LoadInitialParams<String>,
callback: LoadInitialCallback<Account>
) {
enqueueCall(makeInitialCall(params.requestedLoadSize), callback)
searchEnqueueCall(makeInitialCall(params.requestedLoadSize), callback)
}
//This is called to when we get to the bottom of the loaded content, so we want statuses
//older than the given key (params.key)
override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<Account>) {
enqueueCall(makeAfterCall(params.requestedLoadSize, params.key), callback)
searchEnqueueCall(makeAfterCall(params.requestedLoadSize, params.key), callback)
}
private fun enqueueCall(call: Call<Results>, callback: LoadCallback<Account>){
private fun searchEnqueueCall(call: Call<Results>, callback: LoadCallback<Account>) {
call.enqueue(object : Callback<Results> {
override fun onResponse(call: Call<Results>, response: Response<Results>) {

View File

@ -92,16 +92,16 @@ class SearchHashtagFragment: FeedFragment<Tag, SearchHashtagFragment.TagsRecycle
params: LoadInitialParams<String>,
callback: LoadInitialCallback<Tag>
) {
enqueueCall(makeInitialCall(params.requestedLoadSize), callback)
searchEnqueueCall(makeInitialCall(params.requestedLoadSize), callback)
}
//This is called to when we get to the bottom of the loaded content, so we want statuses
//older than the given key (params.key)
override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<Tag>) {
enqueueCall(makeAfterCall(params.requestedLoadSize, params.key), callback)
searchEnqueueCall(makeAfterCall(params.requestedLoadSize, params.key), callback)
}
private fun enqueueCall(call: Call<Results>, callback: LoadCallback<Tag>){
private fun searchEnqueueCall(call: Call<Results>, callback: LoadCallback<Tag>){
call.enqueue(object : Callback<Results> {
override fun onResponse(call: Call<Results>, response: Response<Results>) {

View File

@ -57,16 +57,16 @@ class SearchPostsFragment: PostsFeedFragment(){
params: LoadInitialParams<String>,
callback: LoadInitialCallback<Status>
) {
enqueueCall(makeInitialCall(params.requestedLoadSize), callback)
searchEnqueueCall(makeInitialCall(params.requestedLoadSize), callback)
}
//This is called to when we get to the bottom of the loaded content, so we want statuses
//older than the given key (params.key)
override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<Status>) {
enqueueCall(makeAfterCall(params.requestedLoadSize, params.key), callback)
searchEnqueueCall(makeAfterCall(params.requestedLoadSize, params.key), callback)
}
private fun enqueueCall(call: Call<Results>, callback: LoadCallback<Status>){
private fun searchEnqueueCall(call: Call<Results>, callback: LoadCallback<Status>){
call.enqueue(object : Callback<Results> {
override fun onResponse(call: Call<Results>, response: Response<Results>) {

View File

@ -20,23 +20,23 @@ data class Account(
//Base attributes
override val id: String,
val username: String,
val acct: String,
val url: String, //HTTPS URL
val acct: String = "",
val url: String = "", //HTTPS URL
//Display attributes
val display_name: String?,
val note: String, //HTML
val avatar: String, //URL
val avatar_static: String, //URL
val header: String, //URL
val header_static: String, //URL
val locked: Boolean,
val emojis: List<Emoji>,
val discoverable: Boolean,
val display_name: String? = null,
val note: String = "", //HTML
val avatar: String = "", //URL
val avatar_static: String = "", //URL
val header: String = "", //URL
val header_static: String = "", //URL
val locked: Boolean = false,
val emojis: List<Emoji>? = null,
val discoverable: Boolean = true,
//Statistical attributes
val created_at: String, //ISO 8601 Datetime (maybe can use a date type)
val statuses_count: Int,
val followers_count: Int,
val following_count: Int,
val created_at: String = "", //ISO 8601 Datetime (maybe can use a date type)
val statuses_count: Int = 0,
val followers_count: Int = 0,
val following_count: Int = 0,
//Optional attributes
val moved: Account? = null,
val fields: List<Field>? = emptyList(),

View File

@ -5,9 +5,9 @@ import java.io.Serializable
data class Attachment(
//Required attributes
val id: String,
val type: AttachmentType,
val type: AttachmentType = AttachmentType.image,
val url: String, //URL
val preview_url: String, //URL
val preview_url: String = "", //URL
//Optional attributes
val remote_url: String? = null, //URL
val text_url: String? = null, //URL

View File

@ -0,0 +1,13 @@
package com.h.pixeldroid.objects
import java.io.Serializable
/*
NOT DOCUMENTED, USE WITH CAUTION
*/
data class DiscoverPost(
val type: String?, //This is probably an enum, with these values: https://github.com/pixelfed/pixelfed/blob/700c7805cecc364b68b9cfe20df00608e0f6c465/app/Status.php#L31
val url: String?, //URL to post
val thumb: String? //URL to thumbnail
) : Serializable

View File

@ -0,0 +1,8 @@
package com.h.pixeldroid.objects
import java.io.Serializable
data class DiscoverPosts(
//Required attributes
val posts: List<DiscoverPost>
) : Serializable

View File

@ -9,11 +9,15 @@ import android.util.Log
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.core.text.toSpanned
import android.widget.TextView
import android.widget.LinearLayout
import android.widget.Toast
import android.widget.PopupMenu
import android.widget.ImageView
import android.widget.FrameLayout
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.bumptech.glide.RequestBuilder
import com.google.android.material.tabs.TabLayoutMediator
@ -21,8 +25,10 @@ import com.h.pixeldroid.ImageFragment
import com.h.pixeldroid.R
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.fragments.feeds.PostViewHolder
import com.h.pixeldroid.utils.HtmlUtils.Companion.getDomain
import com.h.pixeldroid.utils.HtmlUtils.Companion.parseHTMLText
import com.h.pixeldroid.utils.ImageConverter
import com.h.pixeldroid.utils.ImageUtils.Companion.downloadImage
import com.h.pixeldroid.utils.PostUtils.Companion.likePostCall
import com.h.pixeldroid.utils.PostUtils.Companion.postComment
import com.h.pixeldroid.utils.PostUtils.Companion.reblogPost
@ -30,10 +36,17 @@ import com.h.pixeldroid.utils.PostUtils.Companion.retrieveComments
import com.h.pixeldroid.utils.PostUtils.Companion.toggleCommentInput
import com.h.pixeldroid.utils.PostUtils.Companion.unLikePostCall
import com.h.pixeldroid.utils.PostUtils.Companion.undoReblogPost
import kotlinx.android.synthetic.main.post_fragment.view.*
import kotlinx.android.synthetic.main.post_fragment.view.postDate
import kotlinx.android.synthetic.main.post_fragment.view.postDomain
import java.io.Serializable
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Date
import kotlin.collections.ArrayList
import kotlinx.android.synthetic.main.post_fragment.view.postPager
import kotlinx.android.synthetic.main.post_fragment.view.postPicture
import kotlinx.android.synthetic.main.post_fragment.view.postTabs
import kotlinx.android.synthetic.main.post_fragment.view.profilePic
/*
Represents a status posted by an account.
@ -42,44 +55,46 @@ https://docs.joinmastodon.org/entities/status/
data class Status(
//Base attributes
override val id: String,
val uri: String,
val created_at: String, //ISO 8601 Datetime (maybe can use a date type)
val uri: String = "",
val created_at: String = "", //ISO 8601 Datetime (maybe can use a date type)
val account: Account,
val content: String, //HTML
val visibility: Visibility,
val sensitive: Boolean,
val spoiler_text: String,
val media_attachments: List<Attachment>?,
val application: Application,
val content: String = "", //HTML
val visibility: Visibility = Visibility.public,
val sensitive: Boolean = false,
val spoiler_text: String = "",
val media_attachments: List<Attachment>? = null,
val application: Application? = null,
//Rendering attributes
val mentions: List<Mention>,
val tags: List<Tag>,
val emojis: List<Emoji>,
val mentions: List<Mention>? = null,
val tags: List<Tag>? = null,
val emojis: List<Emoji>? = null,
//Informational attributes
val reblogs_count: Int,
val favourites_count: Int,
val replies_count: Int,
val reblogs_count: Int = 0,
val favourites_count: Int = 0,
val replies_count: Int = 0,
//Nullable attributes
val url: String?, //URL
val in_reply_to_id: String?,
val in_reply_to_account: String?,
val reblog: Status?,
val poll: Poll?,
val card: Card?,
val language: String?, //ISO 639 Part 1 two-letter language code
val text: String?,
val url: String? = null, //URL
val in_reply_to_id: String? = null,
val in_reply_to_account: String? = null,
val reblog: Status? = null,
val poll: Poll? = null,
val card: Card? = null,
val language: String? = null, //ISO 639 Part 1 two-letter language code
val text: String? = null,
//Authorized user attributes
val favourited: Boolean,
val reblogged: Boolean,
val muted: Boolean,
val bookmarked: Boolean,
val pinned: Boolean
val favourited: Boolean = false,
val reblogged: Boolean = false,
val muted: Boolean = false,
val bookmarked: Boolean = false,
val pinned: Boolean = false
) : Serializable, FeedContent()
{
companion object {
const val POST_TAG = "postTag"
const val POST_FRAG_TAG = "postFragTag"
const val DOMAIN_TAG = "domainTag"
const val DISCOVER_TAG = "discoverTag"
}
fun getPostUrl() : String? = media_attachments?.getOrNull(0)?.url
@ -100,8 +115,8 @@ data class Status(
fun getUsername() : CharSequence {
var name = account.username
if (name.isEmpty()) {
name = account.display_name!!
if (name.isNullOrEmpty()) {
name = account.display_name?: "NoName"
}
return name
}
@ -116,6 +131,36 @@ data class Status(
return "$nShares Shares"
}
private fun ISO8601toDate(dateString : String, textView: TextView, isActivity: Boolean) {
var format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.hhmmss'Z'")
if(dateString.matches("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{6}Z".toRegex())) {
format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.hhmmss'Z'")
} else if(dateString.matches("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}+[0-9]{2}:[0-9]{2}".toRegex())) {
format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss+hh:mm")
}
val now = Date().time
try {
val date: Date = format.parse(dateString)!!
val then = date.time
val formattedDate = android.text.format.DateUtils
.getRelativeTimeSpanString(then, now,
android.text.format.DateUtils.SECOND_IN_MILLIS,
android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE)
textView.text = if(isActivity) "Posted on $date"
else "$formattedDate"
} catch (e: ParseException) {
e.printStackTrace()
}
}
private fun getStatusDomain(domain : String) : String {
val accountDomain = getDomain(account.url)
return if(getDomain(domain) == accountDomain) ""
else " from $accountDomain"
}
private fun setupPostPics(rootView: View, request: RequestBuilder<Drawable>, homeFragment: Fragment) {
//Check whether or not we need to activate the viewPager
if(media_attachments?.size == 1) {
@ -158,7 +203,9 @@ data class Status(
fun setupPost(
rootView: View,
request: RequestBuilder<Drawable>,
homeFragment: Fragment
homeFragment: Fragment,
domain : String,
isActivity : Boolean
) {
//Setup username as a button that opens the profile
val username = rootView.findViewById<TextView>(R.id.username)
@ -178,6 +225,11 @@ data class Status(
nshares.text = this.getNShares()
nshares.setTypeface(null, Typeface.BOLD)
//Convert the date to a readable string
ISO8601toDate(created_at, rootView.postDate, isActivity)
rootView.postDomain.text = getStatusDomain(domain)
//Setup images
ImageConverter.setRoundImageFromURL(
rootView,
@ -194,6 +246,8 @@ data class Status(
//Set comment initial visibility
rootView.findViewById<LinearLayout>(R.id.commentIn).visibility = View.GONE
imagePopUpMenu(rootView, homeFragment.requireActivity())
}
fun setDescription(rootView: View, api : PixelfedAPI, credential: String) {
@ -292,4 +346,31 @@ data class Status(
enum class Visibility : Serializable {
public, unlisted, private, direct
}
fun imagePopUpMenu(view: View, activity: FragmentActivity) {
val anchor = view.findViewById<FrameLayout>(R.id.post_fragment_image_popup_menu_anchor)
if (!media_attachments.isNullOrEmpty() && media_attachments.size == 1) {
view.findViewById<ImageView>(R.id.postPicture).setOnLongClickListener {
PopupMenu(view.context, anchor).apply {
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.image_popup_menu_save_to_gallery -> {
downloadImage(activity, view.context, getPostUrl()!!)
true
}
R.id.image_popup_menu_share_picture -> {
downloadImage(activity, view.context, getPostUrl()!!, share = true)
true
}
else -> false
}
}
inflate(R.menu.image_popup_menu)
show()
}
true
}
}
}
}

View File

@ -32,7 +32,7 @@ class HtmlUtils {
return result.trim().toSpanned()
}
private fun getDomain(urlString: String?): String {
public fun getDomain(urlString: String?): String {
val uri: URI
try {
uri = URI(urlString!!)

View File

@ -0,0 +1,98 @@
package com.h.pixeldroid.utils
import android.app.DownloadManager
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Environment
import android.provider.MediaStore.Images
import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import com.h.pixeldroid.R
import java.io.File
class ImageUtils {
companion object {
fun downloadImage(activity: FragmentActivity, context: Context, url: String, share: Boolean = false) {
var msg = ""
var lastMsg = ""
val directory = File(Environment.DIRECTORY_PICTURES)
if (!directory.exists()) {
directory.mkdirs()
}
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE)
as DownloadManager
val downloadUri = Uri.parse(url)
val title = url.substring(url.lastIndexOf("/") + 1)
val ext = url.substring(url.lastIndexOf("."))
val request = DownloadManager.Request(downloadUri).apply {
setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI
or DownloadManager.Request.NETWORK_MOBILE)
setTitle(title)
setDestinationInExternalPublicDir(directory.toString(), title)
}
val downloadId = downloadManager.enqueue(request)
val query = DownloadManager.Query().setFilterById(downloadId)
Thread(Runnable {
var downloading = true
while (downloading) {
val cursor: Cursor = downloadManager.query(query)
cursor.moveToFirst()
if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
== DownloadManager.STATUS_SUCCESSFUL) {
downloading = false
}
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
if(!share) {
msg = when (status) {
DownloadManager.STATUS_FAILED ->
context.getString(R.string.image_download_failed)
DownloadManager.STATUS_RUNNING ->
context.getString(R.string.image_download_downloading)
DownloadManager.STATUS_SUCCESSFUL ->
context.getString(R.string.image_download_success)
else -> ""
}
if (msg != lastMsg && msg != "") {
activity.runOnUiThread {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
}
lastMsg = msg
}
} else if (status == DownloadManager.STATUS_SUCCESSFUL) {
val icon: Bitmap = BitmapFactory.decodeFile(
Uri.parse(cursor.getString(
cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
)).path
)
val intentShare = Intent(Intent.ACTION_SEND)
intentShare.type = "image/$ext"
val values = ContentValues()
values.put(Images.Media.TITLE, title)
values.put(Images.Media.MIME_TYPE, "image/$ext")
val uri: Uri = context.contentResolver.insert(
Images.Media.EXTERNAL_CONTENT_URI,
values
)!!
try {
val outstream = context.contentResolver.openOutputStream(uri)!!
icon.compress(Bitmap.CompressFormat.JPEG, 100, outstream)
outstream.close()
} catch(e: Exception) {
e.printStackTrace()
}
intentShare.putExtra(Intent.EXTRA_STREAM, uri)
context.startActivity(Intent.createChooser(intentShare, "Share Image"))
}
cursor.close()
}
}).start()
}
}
}

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="-100%p"
android:toXDelta="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:duration="@integer/swipe_animation_duration"/>
</set>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="100%p"
android:toXDelta="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:duration="@integer/swipe_animation_duration"/>
</set>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:toXDelta="-100%p"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:duration="@integer/swipe_animation_duration"/>
</set>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:toXDelta="100%p"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:duration="@integer/swipe_animation_duration"/>
</set>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88z"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,2H4c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#003DFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#010101" android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"/>
</vector>

View File

@ -5,5 +5,5 @@
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
android:pathData="M15.96,10.29l-2.75,3.54 -1.96,-2.36L8.5,15h11l-3.54,-4.71zM3,5L1,5v16c0,1.1 0.9,2 2,2h16v-2L3,21L3,5zM21,1L7,1c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L23,3c0,-1.1 -0.9,-2 -2,-2zM21,17L7,17L7,3h14v14z"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M2.5,4v3h5v12h3L10.5,7h5L15.5,4h-13zM21.5,9h-9v3h3v7h3v-7h3L21.5,9z"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M7.58,4.08L6.15,2.65C3.75,4.48 2.17,7.3 2.03,10.5h2c0.15,-2.65 1.51,-4.97 3.55,-6.42zM19.97,10.5h2c-0.15,-3.2 -1.73,-6.02 -4.12,-7.85l-1.42,1.43c2.02,1.45 3.39,3.77 3.54,6.42zM18,11c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2v-5zM12,22c0.14,0 0.27,-0.01 0.4,-0.04 0.65,-0.14 1.18,-0.58 1.44,-1.18 0.1,-0.24 0.15,-0.5 0.15,-0.78h-4c0.01,1.1 0.9,2 2.01,2z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12.87,15.07l-2.54,-2.51 0.03,-0.03c1.74,-1.94 2.98,-4.17 3.71,-6.53L17,6L17,4h-7L10,2L8,2v2L1,4v1.99h11.17C11.5,7.92 10.44,9.75 9,11.35 8.07,10.32 7.3,9.19 6.69,8h-2c0.73,1.63 1.73,3.17 2.98,4.56l-5.09,5.02L4,19l5,-5 3.11,3.11 0.76,-2.04zM18.5,10h-2L12,22h2l1.12,-3h4.75L21,22h2l-4.5,-12zM15.88,17l1.62,-4.33L19.12,17h-3.24z"/>
</vector>

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/camera_fragment_main_linear_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.NewPostFragment">
<TextureView
android:id="@+id/textureView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<Button
android:id="@+id/takePictureButton"
android:layout_width="36dp"
android:layout_height="30dp"
android:layout_margin="15dp"
android:layout_marginStart="14dp"
android:background="?android:attr/listChoiceIndicatorSingle"
android:gravity="center"
android:padding="15dp"
android:textColor="@color/cardview_light_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.517"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
tools:ignore="MissingConstraints,PrivateResource" />
<ImageView
android:id="@+id/uploadedPictureView"
android:layout_width="366dp"
android:layout_height="532dp"
android:layout_margin="15dp"
android:layout_marginStart="14dp"
android:layout_marginEnd="14dp"
android:contentDescription="@string/upload_a_picture"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/textureView"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintStart_toEndOf="@+id/textureView"
app:layout_constraintTop_toTopOf="@+id/textureView"
app:layout_constraintVertical_bias="0.147"
tools:ignore="MissingConstraints"
tools:srcCompat="@tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/followsFragment"
android:layout_width="match_parent"

View File

@ -20,7 +20,6 @@
android:orientation="vertical">
<ImageView
android:id="@+id/imageView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"

View File

@ -6,12 +6,26 @@
android:layout_height="match_parent"
tools:context=".PostActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/postFragmentSingle"
android:layout_width="0dp"
android:layout_height="0dp"
<ProgressBar
android:id="@+id/postProgressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/postFragmentSingle"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
</androidx.fragment.app.FragmentContainerView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:tools="http://schemas.android.com/tools"
<merge xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
@ -18,7 +18,6 @@
android:background="#88000000">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/new_post_description_input_layout"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
@ -52,4 +51,4 @@
</LinearLayout>
</FrameLayout>
</merge>

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView 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/feedList"
android:name="com.h.pixeldroid.FeedFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/post_fragment" />

View File

@ -7,7 +7,6 @@
android:layout_margin="5dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/notification"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">

View File

@ -10,12 +10,13 @@
android:id="@+id/search"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="center"
android:hint="Search"
app:errorEnabled="true"
app:layout_constraintEnd_toStartOf="@+id/searchButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/searchEditText"
@ -27,24 +28,45 @@
</com.google.android.material.textfield.TextInputLayout>
<ProgressBar
android:id="@+id/searchProgressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search" />
<Button
android:id="@+id/searchButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:text="Search"
app:layout_constraintBottom_toBottomOf="@+id/search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/search" />
<ProgressBar
android:id="@+id/discoverProgressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/discoverRefreshLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/discoverList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
app:layout_constraintTop_toBottomOf="@+id/search" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -28,7 +28,9 @@
android:id="@+id/profilePic"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="10dp"
android:layout_marginTop="10dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:src="@drawable/ic_default_user"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -40,8 +42,16 @@
android:layout_marginStart="12dp"
app:layout_constraintBottom_toBottomOf="@+id/profilePic"
app:layout_constraintStart_toEndOf="@+id/profilePic"
app:layout_constraintTop_toTopOf="@+id/profilePic"
tools:text="Account" />
app:layout_constraintTop_toTopOf="@+id/profilePic"/>
<TextView
android:id="@+id/postDomain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#b3b3b3"
app:layout_constraintBottom_toBottomOf="@+id/profilePic"
app:layout_constraintStart_toEndOf="@+id/username"
app:layout_constraintTop_toTopOf="@+id/profilePic"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/postConstraint"
@ -78,7 +88,19 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@color/browser_actions_bg_grey"/>
tools:src="@color/browser_actions_bg_grey"
android:longClickable="true"/>
<FrameLayout
android:id="@+id/post_fragment_image_popup_menu_anchor"
android:layout_width="1dp"
android:layout_height="1dp"
app:layout_constraintBottom_toBottomOf="@+id/postPicture"
app:layout_constraintEnd_toEndOf="@+id/postPicture"
app:layout_constraintHorizontal_bias="0.1"
app:layout_constraintStart_toStartOf="@+id/postPicture"
app:layout_constraintTop_toTopOf="@+id/postPicture"
app:layout_constraintVertical_bias="0.1" />
</androidx.constraintlayout.widget.ConstraintLayout>
@ -166,17 +188,27 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hyphenationFrequency="full"
app:layout_constraintStart_toStartOf="@+id/usernameDesc"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/usernameDesc"
app:layout_constraintTop_toBottomOf="@+id/usernameDesc"
tools:text="This is a description, describing stuff.\nIt contains multiple lines, and that's okay. It's also got some really long lines, and we love it for it." />
<TextView
android:id="@+id/postDate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textColor="#b3b3b3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/description"
tools:text="time" />
<LinearLayout
android:id="@+id/commentIn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@+id/description"
app:layout_constraintTop_toBottomOf="@+id/postDate"
tools:layout_editor_absoluteX="10dp">
<com.google.android.material.textfield.TextInputLayout

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/image_popup_menu_save_to_gallery"
android:title="@string/save_to_gallery"/>
<item android:id="@+id/image_popup_menu_share_picture"
android:title="@string/share_picture"/>
</menu>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@ -4,7 +4,5 @@
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="nav_header_vertical_spacing">8dp</dimen>
<dimen name="nav_header_height">176dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="text_margin">16dp</dimen>
</resources>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="swipe_animation_duration">300</integer>
</resources>

View File

@ -1,80 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">PixelDroid</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="action_settings">Settings</string>
<string name="menu_slideshow">Account name</string>
<string name="menu_subtitle">Account informations</string>
<string name="menu_account">My Profile</string>
<string name="menu_settings">Settings</string>
<string name="menu_accessibility">Accessibility</string>
<string-array name="list_languages">
<item>English</item>
<item>Français</item>
<item>日本人</item>
</string-array>
<string-array name="list_font_size">
<item>Small</item>
<item>Medium</item>
<item>Big</item>
</string-array>
<string name="title_activity_login">Sign in</string>
<string name="prompt_email">Email</string>
<string name="prompt_password">Password</string>
<string name="action_sign_in">Sign in or register</string>
<string name="action_sign_in_short">Sign in</string>
<string name="welcome">"Welcome !"</string>
<string name="invalid_username">Not a valid username</string>
<string name="invalid_password">Password must be >5 characters</string>
<string name="login_failed">"Login failed"</string>
<string name="invalid_domain">"Invalid domain"</string>
<string name="registration_failed">"Could not register the application with this server"</string>
<string name="browser_launch_failed">"Could not launch a browser, do you have one?"</string>
<string name="auth_failed">"Could not authenticate"</string>
<string name="token_error">"Error getting token"</string>
<string name="title_activity_settings2">Settings</string>
<!-- Preference Titles -->
<string name="messages_header">Messages</string>
<string name="sync_header">Sync</string>
<!-- Messages Preferences -->
<string name="signature_title">Your signature</string>
<string name="reply_title">Default reply action</string>
<!-- Sync Preferences -->
<string name="sync_title">Sync email periodically</string>
<string name="attachment_title">Download incoming attachments</string>
<string name="attachment_summary_on">Automatically download attachments for incoming emails
</string>
<string name="attachment_summary_off">Only download attachments when manually requested</string>
<string name="start_login">Start Login</string>
<string name="no_username">No Username</string>
<string name="followed_notification">%1$s followed you</string>
<string name="mention_notification">%1$s mentioned you</string>
<string name="shared_notification">%1$s shared your post</string>
<string name="liked_notification">%1$s liked your post</string>
<string name="create_a_new_post">Create a new post!</string>
<string name="take_a_picture">Take a picture</string>
<string name="upload_a_picture">Upload a picture</string>
<string name="or">or</string>
<string name="description">Description…</string>
<string name="upload">upload</string>
<string name="send">send</string>
<string name="reconnect">Reconnect</string>
<string name="whats_an_instance">What\'s an instance?</string>
<string name="whats_an_instance">"What's an instance?"</string>
<string name="logout">Logout</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="save_to_gallery">Save to Gallery…</string>
<string name="image_download_failed">Download has been failed, please try again</string>
<string name="image_download_downloading">Downloading…</string>
<string name="image_download_success">Image downloaded successfully</string>
<string name="share_picture">Share picture…</string>
</resources>

View File

@ -15,16 +15,4 @@
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
<style name="posts_title">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_marginBottom">8dp</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:background">@android:color/holo_orange_light</item>
<item name="android:textAppearance">@android:style/TextAppearance.Large</item>
</style>
</resources>

View File

@ -215,9 +215,9 @@ fun assertStatusEqualsToReference(actual: Status){
assert(attchmnt.id == "15888" && attchmnt.type == Attachment.AttachmentType.image && attchmnt.url=="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB.jpeg" &&
attchmnt.preview_url =="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB_thumb.jpeg" &&
attchmnt.remote_url ==null && attchmnt.text_url==null && attchmnt.description==null && attchmnt.blurhash==null )
assert( actual.application.name=="web" && actual.application.website==null && actual.application.vapid_key==null && actual.mentions==emptyList<Mention>())
assert( actual.application!!.name=="web" && actual.application!!.website==null && actual.application!!.vapid_key==null && actual.mentions==emptyList<Mention>())
val firstTag =actual.tags[0]
val firstTag =actual.tags!![0]
assert(firstTag.name=="hiking" && firstTag.url=="https://pixelfed.de/discover/tags/hiking" && firstTag.history==null &&
actual.emojis== emptyList<Emoji>() && actual.reblogs_count==0 && actual.favourites_count==0&& actual.replies_count==0 && actual.url=="https://pixelfed.de/p/Miike/140364967936397312")

BIN
pixeldroid_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

546
pixeldroid_logo.svg Normal file
View File

@ -0,0 +1,546 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
style="enable-background:new"
id="svg102"
version="1.1"
height="49.582283"
width="49.688999">
<metadata
id="metadata106">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs56">
<linearGradient
id="g1"
y2="0.60117739"
x2="0"
y1="0.55806792"
x1="1">
<stop
id="stop2"
offset="0"
stop-color="#FF5C34" />
<stop
id="stop4"
offset="1"
stop-color="#EB0256" />
</linearGradient>
<linearGradient
id="g2"
y2="0"
x2="0.30560157"
y1="1.1191301"
x1="0.5">
<stop
id="stop7"
offset="0"
stop-color="#FFB000" />
<stop
id="stop9"
offset="1"
stop-color="#FF7725" />
</linearGradient>
<linearGradient
xlink:href="#g4"
id="g3"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(0.85441985,1.1703848)"
x1="38.66045"
y1="42.313534"
x2="29.417906"
y2="17.769199" />
<linearGradient
x1="38.66045"
y1="42.313534"
x2="29.417906"
y2="17.769199"
id="g4"
gradientTransform="scale(0.85441985,1.1703848)"
gradientUnits="userSpaceOnUse">
<stop
id="stop20"
stop-color="#21EFE3"
offset="0%" />
<stop
id="stop22"
stop-color="#2598FF"
offset="100%" />
</linearGradient>
<linearGradient
x1="32.778084"
y1="31.292349"
x2="-5.737164"
y2="34.564075"
id="g5"
gradientTransform="scale(0.85441985,1.1703848)"
gradientUnits="userSpaceOnUse">
<stop
id="stop25"
stop-color="#A63FDB"
offset="0%" />
<stop
id="stop27"
stop-color="#FF257E"
offset="100%" />
</linearGradient>
<linearGradient
xlink:href="#g1"
id="g6"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(0.85441985,1.1703848)"
x1="26.799479"
y1="19.639755"
x2="6.4907837"
y2="20.515251" />
<linearGradient
xlink:href="#g1"
id="g7"
x1="26.799479"
y1="19.639755"
x2="6.4907837"
y2="20.515251"
gradientTransform="matrix(0.73238181,-0.44005875,0.60279359,1.0032156,7.0612668,16.678016)"
gradientUnits="userSpaceOnUse" />
<linearGradient
xlink:href="#g2"
id="g8"
x1="15.185128"
y1="33.220253"
x2="9.5916662"
y2="1.0193164"
gradientTransform="matrix(0.87275201,0.73232484,-0.56419841,0.67238452,33.373061,2.1802866)"
gradientUnits="userSpaceOnUse" />
<linearGradient
xlink:href="#g10"
id="g9"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(0.85441984,1.1703848)"
x1="16.690788"
y1="19.195547"
x2="57.873302"
y2="21.720842" />
<linearGradient
x1="16.690788"
y1="19.195547"
x2="57.873302"
y2="21.720842"
id="g10"
gradientTransform="scale(0.85441984,1.1703848)"
gradientUnits="userSpaceOnUse">
<stop
id="stop34"
stop-color="#9EE85D"
offset="0%" />
<stop
id="stop36"
stop-color="#0ED061"
offset="100%" />
</linearGradient>
<linearGradient
x1="40.01442"
y1="3.0503507"
x2="21.610674"
y2="22.693472"
id="g11"
gradientTransform="matrix(0.8028135,0.67363955,-0.61334952,0.73096044,33.373061,2.1802866)"
gradientUnits="userSpaceOnUse">
<stop
id="stop39"
stop-color="#17C934"
offset="0" />
<stop
id="stop41"
stop-color="#03FF6E"
offset="1" />
</linearGradient>
<linearGradient
x1="31.906258"
y1="22.861416"
x2="56.143276"
y2="28.198187"
id="g12"
gradientTransform="matrix(0.67306192,0.5647652,-0.7315899,0.87187364,33.373061,2.1802866)"
gradientUnits="userSpaceOnUse">
<stop
id="stop44"
stop-color="#00FFF0"
offset="0" />
<stop
id="stop46"
stop-color="#0087FF"
offset="1" />
</linearGradient>
<linearGradient
x1="18.604218"
y1="60.088772"
x2="29.551889"
y2="34.263325"
id="g13"
gradientTransform="matrix(0.93316856,0.78302028,-0.52767025,0.62885203,33.373061,2.1802866)"
gradientUnits="userSpaceOnUse">
<stop
id="stop49"
stop-color="#A63FDB"
offset="0" />
<stop
id="stop51"
stop-color="#FF257E"
offset="1" />
</linearGradient>
<linearGradient
xlink:href="#g1"
id="g14"
x1="30.973358"
y1="27.509178"
x2="1.1089396"
y2="28.796618"
gradientTransform="matrix(0.64006516,0.53707767,-0.76930493,0.9168206,33.373061,2.1802866)"
gradientUnits="userSpaceOnUse" />
<linearGradient
xlink:href="#g2"
id="g15"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.87275201,0.73232484,-0.56419841,0.67238452,33.373061,2.1802866)"
x1="15.185128"
y1="33.220253"
x2="9.5916662"
y2="1.0193164" />
<linearGradient
y2="85.349998"
x2="519.52002"
y1="832.48999"
x1="519.52002"
gradientTransform="matrix(-0.09058464,0,0,0.09058464,281.81967,-8.9318819)"
gradientUnits="userSpaceOnUse"
id="linearGradient887"
xlink:href="#a26385e9-dbb5-4257-86ba-bfe3022541ed" />
<linearGradient
id="a26385e9-dbb5-4257-86ba-bfe3022541ed"
x1="519.52002"
y1="832.48999"
x2="519.52002"
y2="85.349998"
gradientTransform="matrix(-1,0,0,1,1096.85,0)"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
stop-color="gray"
stop-opacity="0.25"
id="stop2-3" />
<stop
offset="0.54"
stop-color="gray"
stop-opacity="0.12"
id="stop4-6" />
<stop
offset="1"
stop-color="gray"
stop-opacity="0.1"
id="stop6" />
</linearGradient>
<filter
id="filter447"
style="color-interpolation-filters:sRGB">
<feBlend
id="feBlend449"
in2="BackgroundImage"
mode="overlay" />
</filter>
</defs>
<g
transform="translate(-12.5,-12.917718)"
style="opacity:1;filter:url(#filter447)"
id="layer4">
<path
id="path58"
d="M 37.344501,37.708859 C 33.27843,32.146166 25.502814,30.871306 19.977177,34.86138 14.451539,38.851453 13.268326,46.5955 17.334396,52.158194 l 0.307623,0.420851 C 11.011178,43.906438 10.684942,31.740724 17.295264,22.825752 l 0.117697,-0.1564 c 4.095389,-5.4421 11.877186,-6.487086 17.381128,-2.33404 5.503943,4.153045 6.6458,11.931447 2.550412,17.373547 z"
style="fill:url(#g14)" />
<path
id="path60"
d="m 37.344501,37.708859 c -6.472999,2.189353 -9.877881,9.222288 -7.605018,15.708503 2.272862,6.486214 9.362782,9.969509 15.835779,7.780157 l 0.428828,-0.145041 c -8.049827,2.880268 -17.427966,1.494994 -24.513119,-4.450146 -1.457037,-1.222598 -2.741004,-2.57418 -3.848952,-4.023287 L 17.334396,52.158194 C 13.268326,46.5955 14.451539,38.851453 19.977177,34.86138 25.502814,30.871306 33.27843,32.146166 37.344501,37.708859 Z"
style="fill:url(#g13)" />
<path
id="path62"
d="m 37.344501,37.708859 c 0.04163,6.84562 5.636761,12.42884 12.497071,12.470471 6.860311,0.04163 12.387942,-5.47409 12.346311,-12.319709 l -9.53e-4,-0.156747 c 0.07327,5.679151 -1.798009,11.388946 -5.714387,16.056296 -2.883411,3.436311 -6.515801,5.879029 -10.468453,7.293308 l -0.428828,0.145041 C 39.102265,63.386871 32.012345,59.903576 29.739483,53.417362 27.46662,46.931147 30.871502,39.898212 37.344501,37.708859 Z"
style="fill:url(#g12)" />
<path
id="path64"
d="M 37.344501,37.708859 C 43.881909,39.863866 50.867988,36.343413 52.948347,29.845706 55.028706,23.348 51.415553,16.33359 44.878144,14.178584 l -0.535192,-0.176422 c 3.149862,0.958982 6.167442,2.558035 8.855077,4.813224 5.838829,4.899353 8.898368,11.870026 8.988901,18.887488 l 9.53e-4,0.156747 c 0.04163,6.845619 -5.486,12.361341 -12.346311,12.319709 -6.86031,-0.04163 -12.45544,-5.624851 -12.497071,-12.470471 z"
style="fill:url(#g11)" />
<path
id="path66"
style="fill:url(#g15)"
d="m 37.344501,37.708859 c 4.095388,-5.4421 2.953531,-13.220502 -2.550412,-17.373547 -5.503942,-4.153046 -13.285739,-3.10806 -17.381128,2.33404 l -0.117698,0.156401 c 0.293382,-0.395668 0.600429,-0.784933 0.921193,-1.167205 6.528123,-7.779903 16.895374,-10.466821 26.126496,-7.656386 l 0.535192,0.176422 c 6.537409,2.155006 10.150562,9.169416 8.070203,15.667122 -2.080359,6.497707 -9.066438,10.01816 -15.603846,7.863153 z" />
<g
id="g72"
style="opacity:0.54425222;fill:none"
transform="matrix(-0.37460713,0.92718385,-0.92718518,-0.37460659,81.342244,14.622857)">
<path
id="path68"
d="m 28.379451,9.2701483 0.186983,-0.07462 c 6.393149,-2.551328 13.669757,0.4995351 16.252757,6.8142937 2.583,6.314759 -0.505736,13.502142 -6.898886,16.05347 -0.05343,-1.007011 -0.227732,-1.979458 -0.50829,-2.904343 3.429966,-1.856689 5.75552,-5.45605 5.75552,-9.591913 0,-6.036745 -4.954499,-10.9304939 -11.066184,-10.9304939 -1.305803,0 -2.558782,0.223396 -3.7219,0.6336062 z"
style="fill:url(#g9)" />
<path
id="path70"
d="m 28.379451,9.2701483 0.186983,-0.07462 c 6.393149,-2.551328 13.669757,0.4995351 16.252757,6.8142937 2.583,6.314759 -0.505736,13.502142 -6.898886,16.05347 -0.05343,-1.007011 -0.227732,-1.979458 -0.50829,-2.904343 3.429966,-1.856689 5.75552,-5.45605 5.75552,-9.591913 0,-6.036745 -4.954499,-10.9304939 -11.066184,-10.9304939 -1.305803,0 -2.558782,0.223396 -3.7219,0.6336062 z"
style="mix-blend-mode:overlay;fill:#000000;fill-opacity:0.49988679" />
</g>
<path
id="path74"
style="opacity:0.1;fill:url(#g8)"
d="m 37.344501,37.708859 c 4.095388,-5.4421 2.953531,-13.220502 -2.550412,-17.373547 -5.503942,-4.153046 -13.285739,-3.10806 -17.381128,2.33404 l -0.117698,0.156401 c 0.293382,-0.395668 0.600429,-0.784933 0.921193,-1.167205 6.528123,-7.779903 16.895374,-10.466821 26.126496,-7.656386 l 0.535192,0.176422 c 6.537409,2.155006 10.150562,9.169416 8.070203,15.667122 -2.080359,6.497707 -9.066438,10.01816 -15.603846,7.863153 z" />
<path
id="path76"
style="opacity:0.18013395;fill:url(#g7)"
d="m 17.324475,22.990984 0.121843,-0.160265 c 4.165973,-5.47963 11.974565,-6.61226 17.440978,-2.529799 5.466411,4.082461 6.520625,11.834067 2.354651,17.313698 -0.564448,-0.835659 -1.214702,-1.579436 -1.931538,-2.227718 1.983791,-3.358057 2.123373,-7.64106 -0.0068,-11.186188 -3.109159,-5.1745 -9.87647,-6.817505 -15.115214,-3.669755 -1.119294,0.672539 -2.07825,1.509358 -2.863963,2.460027 z" />
<g
id="g82"
style="opacity:0.18013395;fill:none"
transform="matrix(0.85716853,-0.51503807,0.51503881,0.8571673,7.2277095,16.834214)">
<path
id="path78"
d="m 5.5458544,10.697205 0.1869826,-0.07462 c 6.39315,-2.5513278 13.669757,0.499535 16.252757,6.814294 2.583,6.314758 -0.505736,13.502141 -6.898886,16.053469 -0.05343,-1.007011 -0.227732,-1.979458 -0.50829,-2.904342 3.429966,-1.856689 5.755521,-5.45605 5.755521,-9.591914 0,-6.036745 -4.9545,-10.930493 -11.0661847,-10.930493 -1.3058034,0 -2.5587821,0.223396 -3.7218999,0.633606 z"
style="fill:url(#g6)" />
<path
id="path80"
d="m 5.5458544,10.697205 0.1869826,-0.07462 c 6.39315,-2.5513278 13.669757,0.499535 16.252757,6.814294 2.583,6.314758 -0.505736,13.502141 -6.898886,16.053469 -0.05343,-1.007011 -0.227732,-1.979458 -0.50829,-2.904342 3.429966,-1.856689 5.755521,-5.45605 5.755521,-9.591914 0,-6.036745 -4.9545,-10.930493 -11.0661847,-10.930493 -1.3058034,0 -2.5587821,0.223396 -3.7218999,0.633606 z"
style="mix-blend-mode:multiply;fill:#000000;fill-opacity:0.77284307" />
</g>
<g
id="g88"
style="opacity:0.5841518;fill:none"
transform="matrix(-0.22495138,-0.97437006,0.97437146,-0.22495105,-3.413458,67.921439)">
<path
id="path84"
d="m 10.654093,23.764822 0.186983,-0.07462 c 6.393149,-2.551328 13.669757,0.499535 16.252757,6.814293 2.583,6.314759 -0.505736,13.502142 -6.898886,16.05347 -0.05343,-1.007011 -0.227732,-1.979458 -0.50829,-2.904343 3.429966,-1.856689 5.75552,-5.45605 5.75552,-9.591913 0,-6.036745 -4.954499,-10.930493 -11.066184,-10.930493 -1.305803,0 -2.558782,0.223396 -3.7219,0.633606 z"
style="fill:url(#g5)" />
<path
id="path86"
d="m 10.654093,23.764822 0.186983,-0.07462 c 6.393149,-2.551328 13.669757,0.499535 16.252757,6.814293 2.583,6.314759 -0.505736,13.502142 -6.898886,16.05347 -0.05343,-1.007011 -0.227732,-1.979458 -0.50829,-2.904343 3.429966,-1.856689 5.75552,-5.45605 5.75552,-9.591913 0,-6.036745 -4.954499,-10.930493 -11.066184,-10.930493 -1.305803,0 -2.558782,0.223396 -3.7219,0.633606 z"
style="mix-blend-mode:overlay;fill:#000000;fill-opacity:0.50308539" />
</g>
<g
id="g94"
style="opacity:0.56222097;fill:none"
transform="matrix(-0.99863096,-0.05233596,0.05233603,-0.99862953,69.65441,85.048735)">
<path
id="path90"
d="m 25.135241,22.73235 0.186983,-0.07462 c 6.39315,-2.551328 13.669757,0.499535 16.252757,6.814293 2.583001,6.314759 -0.505736,13.502142 -6.898886,16.05347 -0.05343,-1.007011 -0.227731,-1.979458 -0.50829,-2.904343 3.429966,-1.856689 5.755521,-5.45605 5.755521,-9.591913 0,-6.036745 -4.9545,-10.930494 -11.066185,-10.930494 -1.305803,0 -2.558782,0.223396 -3.7219,0.633607 z"
style="fill:url(#g3)" />
<path
id="path92"
d="m 25.135241,22.73235 0.186983,-0.07462 c 6.39315,-2.551328 13.669757,0.499535 16.252757,6.814293 2.583001,6.314759 -0.505736,13.502142 -6.898886,16.05347 -0.05343,-1.007011 -0.227731,-1.979458 -0.50829,-2.904343 3.429966,-1.856689 5.755521,-5.45605 5.755521,-9.591913 0,-6.036745 -4.9545,-10.930494 -11.066185,-10.930494 -1.305803,0 -2.558782,0.223396 -3.7219,0.633607 z"
style="mix-blend-mode:overlay;fill:#000000" />
</g>
<path
id="path96"
d="m 44.686954,14.115568 0.191202,0.06303 c 6.537408,2.155006 10.150561,9.169416 8.070202,15.667122 -2.080359,6.497706 -9.066438,10.01816 -15.603846,7.863153 0.606364,-0.805759 1.097919,-1.662736 1.477505,-2.551578 3.820968,0.782433 7.916076,-0.48 10.574561,-3.648255 3.88035,-4.624415 3.230624,-11.557934 -1.451204,-15.486449 -1.000306,-0.839354 -2.10374,-1.473623 -3.25842,-1.907021 z"
style="mix-blend-mode:overlay;fill:#000000;fill-opacity:0.49617866" />
</g>
<g
transform="translate(-12.5,-12.917718)"
style="display:inline;opacity:1"
id="layer3">
<g
id="g457">
<g
style="image-rendering:auto"
transform="matrix(0.46524817,0,0,0.46524817,-220.42763,-8.4650049)"
id="layer1">
<g
style="opacity:1"
id="layer2">
<g
transform="translate(319.69342,64.960442)"
id="g277">
<path
style="fill:url(#linearGradient887);stroke-width:0.09058464"
d="m 220.58355,-1.2004795 h 28.353 a 2.8081241,2.8081241 0 0 1 2.80813,2.7990674 V 63.679864 a 2.8081241,2.8081241 0 0 1 -2.80813,2.799067 h -28.353 a 2.8081241,2.8081241 0 0 1 -2.80811,-2.799067 V 1.5976809 a 2.8081241,2.8081241 0 0 1 2.80811,-2.7981604 z"
id="path16" />
<rect
style="fill:#4e5a86;stroke-width:0.09058464"
x="-251.4847"
y="-66.219849"
width="33.451099"
height="66.902199"
rx="2.4340096"
ry="2.4340096"
transform="scale(-1)"
id="rect18" />
<rect
style="fill:#ffffff;stroke-width:0.09058464"
x="-249.02083"
y="-59.996696"
width="28.524202"
height="54.454964"
transform="scale(-1)"
id="rect20" />
<circle
style="fill:#ffffff;stroke-width:0.09058464"
cx="238.12979"
cy="2.4292343"
r="0.25907207"
id="circle22" />
<rect
style="fill:#ffffff;stroke-width:0.09058464"
x="-236.83263"
y="-2.6883314"
width="5.7050209"
height="0.51905"
rx="0.090584643"
ry="0.090584643"
transform="scale(-1)"
id="rect24" />
<circle
style="fill:#ffffff;stroke-width:0.09058464"
cx="234.88864"
cy="62.589226"
r="1.8153163"
id="circle26" />
<ellipse
style="opacity:0.1;fill:#6c63ff;stroke-width:0.09058464"
cx="233.40039"
cy="64.434441"
rx="23.008501"
ry="4.4386477"
id="ellipse28" />
<rect
style="opacity:0.1;fill:#6c63ff;stroke-width:0.09058464"
x="223.75311"
y="13.978788"
width="22.012068"
height="29.44001"
id="rect30" />
<path
style="fill:none;stroke:#535461;stroke-width:0.18116929;stroke-miterlimit:10"
d="m 252.89873,65.466194 c 0,0 -1.31168,-6.64529 1.38956,-11.199889 a 10.263241,10.263241 0 0 0 1.41583,-6.335483 17.260904,17.260904 0 0 0 -0.63409,-3.092559"
stroke-miterlimit="10"
id="path74-7" />
<path
style="fill:#6c63ff;stroke-width:0.09058464"
d="m 256.75672,41.957668 c -0.0724,0.792613 -1.72111,2.960306 -1.72111,2.960306 0,0 -1.2247,-2.43129 -1.15134,-3.22481 a 1.4421076,1.4421076 0 1 1 2.87154,0.263596 z"
id="path76-5" />
<path
style="fill:#6c63ff;stroke-width:0.09058464"
d="m 258.72694,46.567519 c -0.49187,0.625941 -3.0527,1.550813 -3.0527,1.550813 0,0 0.29348,-2.706676 0.78537,-3.33261 a 1.4421076,1.4421076 0 0 1 2.26462,1.781797 z"
id="path78-3" />
<path
style="fill:#6c63ff;stroke-width:0.09058464"
d="m 257.93343,54.064307 c -0.74099,0.291683 -3.41777,-0.202902 -3.41777,-0.202902 0,0 1.61965,-2.188529 2.36063,-2.480212 a 1.4421076,1.4421076 0 0 1 1.05714,2.683119 z"
id="path80-5" />
<path
style="fill:#6c63ff;stroke-width:0.09058464"
d="m 256.05651,59.601743 c -0.68121,0.413066 -3.40328,0.377739 -3.40328,0.377739 0,0 1.22653,-2.430383 1.90771,-2.843449 a 1.4421076,1.4421076 0 1 1 1.49557,2.46571 z"
id="path82" />
<path
style="fill:#6c63ff;stroke-width:0.09058464"
d="m 252.64508,47.995134 c 0.51816,0.605105 3.1152,1.420368 3.1152,1.420368 0,0 -0.40763,-2.69218 -0.92486,-3.296376 a 1.4421076,1.4421076 0 1 0 -2.19034,1.876008 z"
id="path84-6" />
<path
style="fill:#6c63ff;stroke-width:0.09058464"
d="m 250.53174,54.498203 c 0.67486,0.422129 3.39782,0.423036 3.39782,0.423036 0,0 -1.1939,-2.445787 -1.86965,-2.868821 a 1.4421076,1.4421076 0 1 0 -1.52817,2.445785 z"
id="path86-2" />
<path
style="fill:#6c63ff;stroke-width:0.09058464"
d="m 249.34418,61.101827 c 0.59424,0.529921 3.27735,0.991902 3.27735,0.991902 0,0 -0.76362,-2.61337 -1.35876,-3.143285 a 1.4421076,1.4421076 0 1 0 -1.91949,2.152287 z"
id="path88" />
<path
style="fill:#6c63ff;stroke-width:0.09058464"
d="m 231.56577,10.289987 a 3.4295348,3.4295348 0 0 0 -3.42952,-3.4295355 h -18.68581 a 3.4295359,3.4295359 0 0 0 0,6.8590705 l 15.62314,-0.02988 3.59348,3.135132 v -3.15144 A 3.4268171,3.4268171 0 0 0 231.56577,10.29 Z"
id="path90-9" />
<circle
style="opacity:0.2;fill:#ffffff;stroke-width:0.09058464"
cx="210.04285"
cy="10.407731"
r="1.1830356"
id="circle92" />
<circle
style="opacity:0.2;fill:#ffffff;stroke-width:0.09058464"
cx="214.06299"
cy="10.407731"
r="1.1830356"
id="circle94" />
<circle
style="opacity:0.2;fill:#ffffff;stroke-width:0.09058464"
cx="218.08408"
cy="10.407731"
r="1.1830356"
id="circle96" />
<rect
style="fill:#6c63ff;stroke-width:0.09058464"
x="223.70779"
y="48.129181"
width="9.420804"
height="2.8081243"
id="rect98" />
<rect
style="opacity:0.1;fill:#6c63ff;stroke-width:0.09058464"
x="236.29907"
y="48.129181"
width="9.420804"
height="2.8081243"
id="rect100" />
<path
style="fill:#ee7b0e;fill-opacity:1;stroke:none;stroke-width:0.39877287px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 226.43202,48.543745 c 0.1552,-5.07183 1.33557,-9.88736 2.51385,-14.7034 l 11.18922,0.20848 c 0.96681,2.53872 1.47268,4.51385 2.04322,7.33063 0.72228,-0.10617 1.63613,0.58322 2.40468,1.00917 0.95259,0.7607 1.01757,1.86823 1.04246,2.98843 -0.91888,0.51959 -1.88691,0.6354 -3.34639,0.75604 -0.87129,-0.6299 -2.21927,-1.3383 -3.68281,-3.00383 -2.18994,0.37489 -4.31887,0.53629 -6.24847,0 l -2.38677,6.38541 c -1.38616,0.20092 -2.66287,0.12815 -3.52899,-0.97093 z"
id="path961-1-6" />
<path
style="fill:#ee7b0e;fill-opacity:1;stroke:none;stroke-width:0.39877287px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 230.38693,21.816615 c -1.8751,2.79634 -1.73552,5.99558 -2.35884,9.04226 -1.27705,6.24204 14.0321,8.19235 12.97369,0.29487 -0.43292,-3.23017 -0.10602,-6.71364 -2.064,-9.4354 -0.9447,0.85411 -1.85396,1.72342 -2.16227,2.85027 -1.60511,-0.58881 -2.9775,-0.24678 -4.32457,0.19659 -0.47648,-1.30014 -1.21855,-2.20191 -2.06401,-2.94859 z"
id="path959-9-7" />
<rect
style="opacity:1;fill:#481800;fill-opacity:1;stroke:none;stroke-width:0.39877287;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect963-4-5"
width="0.59073234"
height="2.6409397"
x="231.44785"
y="27.620195"
ry="0.29536617" />
<rect
style="opacity:1;fill:#481800;fill-opacity:1;stroke:none;stroke-width:0.39877287;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect963-7-7-3"
width="0.5907324"
height="2.6409397"
x="236.01736"
y="27.585466"
ry="0.2953662" />
<path
style="fill:#481800;fill-opacity:1;stroke:none;stroke-width:0.39877287px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 235.16596,30.747655 h -2.2587 l 1.00773,1.28571 z"
id="path980-8-5" />
<path
style="fill:none;stroke:#481800;stroke-width:0.39940086;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 232.87602,34.040375 c 1.76209,-0.0973 3.1945,0.21621 4.10041,-1.25096"
id="path982-4-6" />
<path
style="fill:#a9550d;fill-opacity:1;stroke:none;stroke-width:0.39877287px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 228.07955,30.569965 c -0.0183,0.0962 -0.0318,0.19315 -0.0514,0.28896 -0.6013,2.93911 2.47426,4.92651 5.78696,5.3824 -0.0898,-0.52384 0,0 -0.0898,-0.52384 -2.62078,-0.29956 -5.06396,-1.91042 -5.64574,-5.14752 z"
id="path959-0-5-2" />
<path
style="fill:#a9550d;fill-opacity:1;stroke:none;stroke-width:0.39877287px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 228.94561,33.840385 c -1.17827,4.81606 -2.35813,9.63137 -2.51335,14.70318 0.86613,1.09906 2.14283,1.17215 3.52898,0.97123 l 0.17524,-0.46964 c -1.23366,0.12889 -2.35545,-0.0221 -3.13955,-1.01718 0.14952,-4.88775 1.25361,-9.53746 2.38796,-14.17902 z m 3.96747,8.77299 -0.22275,0.59505 c 1.83832,0.42971 3.84412,0.27353 5.90607,-0.0794 1.46352,1.66553 2.81111,2.37413 3.68241,3.00403 1.45945,-0.12058 2.42783,-0.23668 3.34673,-0.75625 -0.001,-0.083 -0.009,-0.16413 -0.0119,-0.2469 -0.76129,0.29505 -1.60946,0.39167 -2.76962,0.48756 -0.87128,-0.62991 -2.21966,-1.33851 -3.6832,-3.00405 -2.18992,0.37489 -4.31838,0.53631 -6.24796,0 z"
id="path961-3-0-9" />
<path
style="fill:#bf620f;fill-opacity:1;stroke:none;stroke-width:0.39877287px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 242.17831,41.379455 c -0.44726,0.12043 -0.9405,0.14875 -1.25795,0.52864 l -0.29879,-0.30134 c 0.47694,-0.32768 0.9873,-0.45461 1.55674,-0.2273 z"
id="path1048-3-1" />
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 26 KiB