Compare commits

...

10 Commits

Author SHA1 Message Date
Fred 71f1281e2b Merge branch 'arrange_tabs' into 'master'
Draft: Rearrange tabs

See merge request pixeldroid/PixelDroid!570
2024-03-31 21:55:18 +00:00
Matthieu 04324577ea Merge branch 'dependencies_upgrade' into 'master'
Dependencies upgrade

See merge request pixeldroid/PixelDroid!589
2024-03-29 08:33:37 +00:00
Matthieu 73f08e5a5f Update dependencies 2024-03-29 09:03:48 +01:00
Matthieu a7feab380b Merge remote-tracking branch 'origin/master' 2024-03-23 15:39:07 +01:00
Matthieu 018f893388 Adapt to multiplied version numbers 2024-03-16 10:10:39 +01:00
Fred bbcaa46b36 Remove useless imports in settings 2024-03-07 08:35:31 +00:00
Fred b4330d7ac3 Organize settings more consistently 2024-03-07 08:35:31 +00:00
Fred 073a6a4489 Create fragments according to tab settings 2024-03-07 08:35:31 +00:00
Fred 1cfb9aecb3 Load bottom menu programmatically 2024-03-07 08:35:31 +00:00
Fred a46c37766f Create settings entry for arranging tabs 2024-03-07 08:35:31 +00:00
16 changed files with 444 additions and 92 deletions

View File

@ -223,21 +223,21 @@ dependencies {
implementation 'com.google.android.material:material:1.11.0'
//Dagger (dependency injection)
implementation 'com.google.dagger:dagger:2.50'
ksp 'com.google.dagger:dagger-compiler:2.50'
implementation 'com.google.dagger:dagger:2.51'
ksp 'com.google.dagger:dagger-compiler:2.51'
implementation("com.google.dagger:hilt-android:2.50")
ksp "com.google.dagger:hilt-compiler:2.50"
implementation('com.google.dagger:hilt-android:2.51')
ksp 'com.google.dagger:hilt-compiler:2.51'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.10.0'
implementation 'com.squareup.retrofit2:converter-gson:2.10.0'
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.10.0'
implementation 'io.reactivex.rxjava3:rxjava:3.1.8'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
implementation 'com.github.connyduck:sparkbutton:4.1.0'
implementation 'org.pixeldroid.pixeldroid:android-media-editor:1.7'
implementation 'org.pixeldroid.pixeldroid:android-media-editor:2.0'
implementation project(path: ':scrambler')
implementation project(path: ':pixel_common')

View File

@ -9,6 +9,7 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
@ -16,6 +17,8 @@ import androidx.activity.addCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.PopupMenu
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@ -25,6 +28,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.paging.ExperimentalPagingApi
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
@ -48,6 +52,8 @@ import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
import com.mikepenz.materialdrawer.widget.AccountHeaderView
import kotlinx.coroutines.launch
import org.json.JSONArray
import org.json.JSONObject
import org.ligi.tracedroid.sending.sendTraceDroidStackTracesIfExist
import org.pixeldroid.app.databinding.ActivityMainBinding
import org.pixeldroid.app.postCreation.camera.CameraFragment
@ -65,11 +71,14 @@ import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.db.updateUserInfoDb
import org.pixeldroid.app.utils.hasInternet
import org.pixeldroid.app.utils.loadDefaultMenuTabs
import org.pixeldroid.app.utils.loadJsonMenuTabs
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.INSTANCE_NOTIFICATION_TAG
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.SHOW_NOTIFICATION_TAG
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.USER_NOTIFICATION_TAG
import org.pixeldroid.app.utils.notificationsWorker.enablePullNotifications
import org.pixeldroid.app.utils.notificationsWorker.removeNotificationChannelsFromAccount
import org.pixeldroid.app.utils.toList
import java.time.Instant
@ -86,7 +95,6 @@ class MainActivity : BaseActivity() {
private lateinit var binding: ActivityMainBinding
@OptIn(ExperimentalPagingApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen().setOnExitAnimationListener {
it.remove()
@ -112,24 +120,8 @@ class MainActivity : BaseActivity() {
sendTraceDroidStackTracesIfExist("contact@pixeldroid.org", this)
setupDrawer()
val tabs: List<() -> Fragment> = listOf(
{
PostFeedFragment<HomeStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", true) }
}
},
{ SearchDiscoverFragment() },
{ CameraFragment() },
{ NotificationsFragment() },
{
PostFeedFragment<PublicFeedStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", false) }
}
}
)
setupTabs(tabs)
setupTabs()
val showNotification: Boolean = intent.getBooleanExtra(SHOW_NOTIFICATION_TAG, false)
@ -396,29 +388,103 @@ class MainActivity : BaseActivity() {
touchSlopField.set(recyclerView, touchSlop*NestedScrollableHost.touchSlopModifier)
}
private fun setupTabs(tab_array: List<() -> Fragment>){
@OptIn(ExperimentalPagingApi::class)
private fun setupTabs() {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
val tabsCheckedString = sharedPreferences.getString("tabsChecked", null)
val pageIds = listOf(R.id.page_1, R.id.page_2, R.id.page_3, R.id.page_4, R.id.page_5)
fun getDrawable(title: String): Drawable? {
val resId = when (title) {
getString(R.string.home_feed) -> R.drawable.selector_home_feed
getString(R.string.search_discover_feed) -> R.drawable.ic_search_white_24dp
getString(R.string.create_feed) -> R.drawable.selector_camera
getString(R.string.notifications_feed) -> R.drawable.selector_notifications
getString(R.string.public_feed) -> R.drawable.ic_filter_black_24dp
else -> 0
}
if (resId == 0) {
return null
} else {
return AppCompatResources.getDrawable(applicationContext, resId)
}
}
fun getFragment(title: String): (() -> Fragment)? {
return when (title) {
getString(R.string.home_feed) -> { {
PostFeedFragment<HomeStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", true) }
}
} }
getString(R.string.search_discover_feed) -> { { SearchDiscoverFragment() } }
getString(R.string.create_feed) -> { { CameraFragment() } }
getString(R.string.notifications_feed) -> { { NotificationsFragment() } }
getString(R.string.public_feed) -> { {
PostFeedFragment<PublicFeedStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", false) }
}
} }
else -> null
}
}
val tabs = if (tabsCheckedString == null) {
// Load default menu
loadDefaultMenuTabs(applicationContext, binding.root)
} else {
// Get current menu visibility and order from settings
val tabsChecked = loadJsonMenuTabs(tabsCheckedString).filter { it.second }.map { it.first }
val bottomNavigationMenu: Menu = binding.tabs.menu
bottomNavigationMenu.clear()
tabsChecked.zip(pageIds).forEach { (tabTitle, pageId) ->
with(bottomNavigationMenu.add(0, pageId, Menu.NONE, tabTitle)) {
val tabIcon = getDrawable(tabTitle)
if (tabIcon != null) {
icon = tabIcon
}
}
}
tabsChecked
}
val tabArray: List<() -> Fragment> = tabs.map { getFragment(it)!! }
binding.viewPager.reduceDragSensitivity()
binding.viewPager.adapter = object : FragmentStateAdapter(this) {
override fun createFragment(position: Int): Fragment {
return tab_array[position]()
return tabArray[position]()
}
override fun getItemCount(): Int {
return tab_array.size
return tabArray.size
}
}
val notificationId = tabs.zip(pageIds).find {
it.first == getString(R.string.notifications_feed)
}?.second
fun doAtPageId(pageId: Int): Int {
if (notificationId != null && pageId == notificationId) {
setNotificationBadge(false)
}
return pageId
}
binding.viewPager.registerOnPageChangeCallback(object : OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
val selected = when(position){
0 -> R.id.page_1
1 -> R.id.page_2
2 -> R.id.page_3
3 -> {
setNotificationBadge(false)
R.id.page_4
}
4 -> R.id.page_5
0 -> doAtPageId(R.id.page_1)
1 -> doAtPageId(R.id.page_2)
2 -> doAtPageId(R.id.page_3)
3 -> doAtPageId(R.id.page_4)
4 -> doAtPageId(R.id.page_5)
else -> null
}
if (selected != null) {

View File

@ -39,6 +39,8 @@ import org.pixeldroid.app.utils.bindingLifecycleAware
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
import org.pixeldroid.app.utils.fileExtension
import org.pixeldroid.app.utils.getMimeType
import org.pixeldroid.media_editor.common.PICTURE_POSITION
import org.pixeldroid.media_editor.common.PICTURE_URI
import org.pixeldroid.media_editor.photoEdit.PhotoEditActivity
import org.pixeldroid.media_editor.videoEdit.VideoEditActivity
import java.io.File
@ -47,7 +49,6 @@ import java.text.SimpleDateFormat
import java.util.Locale
class PostCreationFragment : BaseFragment() {
private var binding: FragmentPostCreationBinding by bindingLifecycleAware()
private val model: PostCreationViewModel by activityViewModels()
@ -307,7 +308,7 @@ class PostCreationFragment : BaseFragment() {
ActivityResultContracts.StartActivityForResult()){
result: ActivityResult? ->
if (result?.resultCode == Activity.RESULT_OK && result.data != null) {
val position: Int = result.data!!.getIntExtra(PhotoEditActivity.PICTURE_POSITION, 0)
val position: Int = result.data!!.getIntExtra(PICTURE_POSITION, 0)
model.modifyAt(position, result.data!!)
?: Toast.makeText(requireActivity(), R.string.error_editing, Toast.LENGTH_SHORT).show()
} else if(result?.resultCode != Activity.RESULT_CANCELED){
@ -320,8 +321,8 @@ class PostCreationFragment : BaseFragment() {
requireActivity(),
if (model.getPhotoData().value!![position].video) VideoEditActivity::class.java else PhotoEditActivity::class.java
)
.putExtra(PhotoEditActivity.PICTURE_URI, model.getPhotoData().value!![position].imageUri)
.putExtra(PhotoEditActivity.PICTURE_POSITION, position)
.putExtra(PICTURE_URI, model.getPhotoData().value!![position].imageUri)
.putExtra(PICTURE_POSITION, position)
editResultContract.launch(intent)
}

View File

@ -40,6 +40,7 @@ import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import org.pixeldroid.app.utils.fileExtension
import org.pixeldroid.app.utils.getMimeType
import org.pixeldroid.media_editor.common.PICTURE_URI
import org.pixeldroid.media_editor.videoEdit.VideoEditActivity
import retrofit2.HttpException
import java.io.File
@ -299,7 +300,7 @@ class PostCreationViewModel @Inject constructor(
}
}
} else {
imageUri = data.getStringExtra(org.pixeldroid.media_editor.photoEdit.PhotoEditActivity.PICTURE_URI)!!.toUri()
imageUri = data.getStringExtra(PICTURE_URI)!!.toUri()
val (imageSize, imageVideo) = getSizeAndVideoValidate(imageUri, position)
size = imageSize
video = imageVideo

View File

@ -1,11 +1,18 @@
package org.pixeldroid.app.settings
import android.annotation.SuppressLint
import androidx.appcompat.app.AlertDialog
import android.app.Dialog
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.XmlResourceParser
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
@ -14,12 +21,22 @@ import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.button.MaterialButton
import com.google.android.material.checkbox.MaterialCheckBox
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.json.JSONArray
import org.json.JSONObject
import org.pixeldroid.app.MainActivity
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.SettingsBinding
import org.pixeldroid.common.ThemedActivity
import org.pixeldroid.app.utils.loadDefaultMenuTabs
import org.pixeldroid.app.utils.loadJsonMenuTabs
import org.pixeldroid.app.utils.setThemeFromPreferences
import org.pixeldroid.common.ThemedActivity
class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
@ -103,6 +120,8 @@ class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceC
dialogFragment = ColorPreferenceDialog((preference as ColorPreference?)!!)
} else if(preference.key == "language"){
dialogFragment = LanguageSettingFragment()
} else if (preference.key == "arrange_tabs") {
dialogFragment = ArrangeTabsFragment()
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0)
@ -132,6 +151,7 @@ class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceC
}
class LanguageSettingFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val list: MutableList<String> = mutableListOf()
// IDE doesn't find it, but compiling works apparently?
@ -175,3 +195,131 @@ class LanguageSettingFragment : DialogFragment() {
}.create()
}
}
class ArrangeTabsFragment: DialogFragment() {
@SuppressLint("ClickableViewAccessibility")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val inflater: LayoutInflater = requireActivity().layoutInflater
val dialogView: View = inflater.inflate(R.layout.layout_tabs_arrange, null)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val tabsCheckedString = sharedPreferences.getString("tabsChecked", null)
val map = if (tabsCheckedString == null) {
// Load default menu
val list = loadDefaultMenuTabs(requireContext(), dialogView)
list.zip(List(list.size){true}.toTypedArray()).toMutableList()
} else {
// Get current menu visibility and order from settings
loadJsonMenuTabs(tabsCheckedString).toMutableList()
}
val listFeed: RecyclerView = dialogView.findViewById(R.id.tabs)
val listAdapter = ListViewAdapter(map)
listFeed.adapter = listAdapter
listFeed.layoutManager = LinearLayoutManager(requireActivity())
val callback = object: ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) {
override fun onMove(
recyclerView: RecyclerView,
source: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
listAdapter.onItemMove(source.bindingAdapterPosition, target.bindingAdapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// Do nothing, all items should remain in the list
}
}
val itemTouchHelper = ItemTouchHelper(callback)
itemTouchHelper.attachToRecyclerView(listFeed)
val dialog = MaterialAlertDialogBuilder(requireContext()).apply {
setIcon(R.drawable.outline_bottom_navigation)
setTitle(R.string.arrange_tabs_summary)
setView(dialogView)
setNegativeButton(android.R.string.cancel) { _, _ -> }
setPositiveButton(android.R.string.ok) { _, _ ->
// Save values into preferences
val tabsChecked = listAdapter.tabsChecked.toList()
val tabsJson = JSONArray()
val checkedJson = JSONArray()
tabsChecked.forEach { (k, v) ->
tabsJson.put(k)
checkedJson.put(v.toString())
}
val tabsCheckedJson = JSONObject().apply {
put("tabs", tabsJson)
put("checked", checkedJson)
}.toString()
sharedPreferences.edit().putString("tabsChecked", tabsCheckedJson).apply()
}
}.create()
return dialog
}
inner class ListViewAdapter(val tabsChecked: MutableList<Pair<String, Boolean>>):
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = FrameLayout.inflate(context, R.layout.layout_tab, null)
// Make sure the layout occupies full width
view.layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
)
return object: RecyclerView.ViewHolder(view) {}
}
@SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val textView: MaterialButton = holder.itemView.findViewById(R.id.textView)
val checkBox: MaterialCheckBox = holder.itemView.findViewById(R.id.checkBox)
val dragHandle: ImageView = holder.itemView.findViewById(R.id.dragHandle)
// Set content of each entry
textView.text = tabsChecked[position].first
checkBox.isChecked = tabsChecked[position].second
// Also interact with checkbox when button is clicked
textView.setOnClickListener {
val isCheckedNew = !tabsChecked[position].second
tabsChecked[position] = Pair(tabsChecked[position].first, isCheckedNew)
checkBox.isChecked = isCheckedNew
// Disable OK button when no tab is selected or when strictly more than 5 tabs are selected
val maxItemCount = BottomNavigationView(requireContext()).maxItemCount // = 5
(requireDialog() as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled =
with (tabsChecked.count { (_, v) -> v }) { this in 1..maxItemCount}
}
// Also highlight button when checkbox is clicked
checkBox.setOnTouchListener { _, motionEvent ->
textView.dispatchTouchEvent(motionEvent)
}
// Do not highlight the button when the drag handle is touched
dragHandle.setOnTouchListener { _, _ -> true }
}
override fun getItemCount(): Int {
return tabsChecked.size
}
fun onItemMove(from: Int, to: Int) {
val previous = tabsChecked.removeAt(from)
tabsChecked.add(to, previous)
notifyItemMoved(from, to)
notifyItemChanged(to) // necessary to avoid checkBox issues
}
}
}

View File

@ -11,11 +11,13 @@ import android.net.ConnectivityManager
import android.net.Uri
import android.os.Build
import android.util.DisplayMetrics
import android.view.View
import android.view.WindowManager
import android.webkit.MimeTypeMap
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.PopupMenu
import androidx.browser.customtabs.CustomTabsIntent
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
@ -28,6 +30,8 @@ import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializer
import okhttp3.HttpUrl
import org.json.JSONArray
import org.json.JSONObject
import org.pixeldroid.app.R
import java.time.Instant
import java.time.format.DateTimeFormatter
@ -192,4 +196,24 @@ fun <T> Fragment.bindingLifecycleAware(): ReadWriteProperty<Fragment, T> =
binding = value
this@bindingLifecycleAware.viewLifecycleOwner.lifecycle.addObserver(this)
}
}
}
fun JSONArray.toList(): List<String> {
return (0 until this.length()).map { this.get(it).toString() }
}
fun loadDefaultMenuTabs(context: Context, anchor: View): List<String> {
return with(PopupMenu(context, anchor)) {
val menu = this.menu
menuInflater.inflate(R.menu.bottom_navigation_main, menu)
(0 until menu.size()).map { menu.getItem(it).title.toString() }
}
}
fun loadJsonMenuTabs(jsonString: String): List<Pair<String, Boolean>> {
val tabsCheckedJson = JSONObject(jsonString)
val tabs = tabsCheckedJson.getJSONArray("tabs").toList()
val checked = tabsCheckedJson.getJSONArray("checked").toList().map { v -> v.toBoolean() }
return tabs.zip(checked)
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="960" android:viewportWidth="960"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/colorOnBackground" android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,600L760,600L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,600ZM200,680L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,680L200,680ZM200,680L200,680L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,680Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="960" android:viewportWidth="960"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/colorOnBackground" android:pathData="M346,820L100,574Q90,564 85,552Q80,540 80,527Q80,514 85,502Q90,490 100,480L330,251L255,176Q242,163 241.5,145Q241,127 254,113Q267,99 286,99Q305,99 319,113L686,480Q696,490 700.5,502Q705,514 705,527Q705,540 700.5,552Q696,564 686,574L440,820Q430,830 418,835Q406,840 393,840Q380,840 368,835Q356,830 346,820ZM393,314L179,528Q179,528 179,528Q179,528 179,528L607,528Q607,528 607,528Q607,528 607,528L393,314ZM792,840Q756,840 731,814.5Q706,789 706,752Q706,725 719.5,701Q733,677 750,654L769,630Q778,619 792.5,618.5Q807,618 816,629L836,654Q852,677 866,701Q880,725 880,752Q880,789 854,814.5Q828,840 792,840Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="960" android:viewportWidth="960"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/colorOnBackground" android:pathData="M200,600Q183,600 171.5,588.5Q160,577 160,560Q160,543 171.5,531.5Q183,520 200,520L760,520Q777,520 788.5,531.5Q800,543 800,560Q800,577 788.5,588.5Q777,600 760,600L200,600ZM200,440Q183,440 171.5,428.5Q160,417 160,400Q160,383 171.5,371.5Q183,360 200,360L760,360Q777,360 788.5,371.5Q800,383 800,400Q800,417 788.5,428.5Q777,440 760,440L200,440Z"/>
</vector>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cornerRadius="0dp"
android:gravity="start|center_vertical"
android:textColor="?attr/colorOnBackground"
android:textAllCaps="false"
android:textAppearance="?attr/textAppearanceBody1"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingStart="75dp"
android:paddingEnd="75dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="40dp"
app:layout_constraintBottom_toBottomOf="@id/textView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/textView"
app:layout_constraintVertical_bias="0.5"
tools:ignore="RtlSymmetry" />
<ImageView
android:id="@+id/dragHandle"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:src="@drawable/rounded_drag_handle"
android:paddingEnd="15dp"
android:paddingStart="15dp"
android:layout_marginEnd="15dp"
app:layout_constraintBottom_toBottomOf="@id/textView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/textView"
app:layout_constraintVertical_bias="0.5"
tools:ignore="RtlSymmetry" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.widget.Space
android:id="@+id/titleDividerNoCustom"
android:layout_width="match_parent"
android:layout_height="@dimen/m3_alert_dialog_title_bottom_margin"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -348,4 +348,7 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
<string name="continue_post_creation">Continue</string>
<string name="extraneous_pictures_stories">Pictures after the first were removed but can be restored by switching back to creating a Post</string>
<string name="story_duration">Story Duration</string>
<string name="arrange_tabs_summary">Arrange tabs</string>
<string name="arrange_tabs_description">Change visibility and order of tabs</string>
<string name="content_header">Content</string>
</resources>

View File

@ -17,54 +17,64 @@
android:title="@string/accentColorTitle"
android:key="themeColor"
android:defaultValue="0"
android:summary="@string/accentColorSummary" />
android:summary="@string/accentColorSummary"
app:icon="@drawable/rounded_colors"/>
<ListPreference
app:key="language"
app:title="@string/language"
app:icon="@drawable/translate_black_24dp" />
</PreferenceCategory>
<ListPreference
app:key="language"
app:title="@string/language"
app:icon="@drawable/translate_black_24dp" />
<PreferenceCategory app:title="@string/content_header">
<ListPreference
android:key="arrange_tabs"
android:title="@string/arrange_tabs_summary"
android:summary="@string/arrange_tabs_description"
android:icon="@drawable/outline_bottom_navigation" />
<CheckBoxPreference app:key="always_show_nsfw" app:title="@string/always_show_nsfw"
app:icon="@drawable/eye_black_24dp" android:defaultValue="false"
android:summary="@string/summary_always_show_nsfw"/>
<CheckBoxPreference app:key="always_show_nsfw" app:title="@string/always_show_nsfw"
app:icon="@drawable/eye_black_24dp" android:defaultValue="false"
android:summary="@string/summary_always_show_nsfw"/>
<Preference android:title="@string/notifications_settings"
android:key="notification"
android:summary="@string/notifications_settings_summary"
app:icon="@drawable/ic_baseline_notifications_active_24">
<intent android:action="android.settings.APP_NOTIFICATION_SETTINGS">
<extra android:name="android.provider.extra.APP_PACKAGE"
android:value="@string/application_id" />
</intent>
</Preference>
<EditTextPreference android:title="@string/description_template"
android:key="prefill_description"
android:summary="@string/description_template_summary"
app:icon="@drawable/note" />
<EditTextPreference android:title="@string/description_template"
android:key="prefill_description"
android:summary="@string/description_template_summary"
app:icon="@drawable/note" />
<Preference android:title="@string/notifications_settings"
android:key="notification"
android:summary="@string/notifications_settings_summary"
app:icon="@drawable/ic_baseline_notifications_active_24">
<intent android:action="android.settings.APP_NOTIFICATION_SETTINGS">
<extra android:name="android.provider.extra.APP_PACKAGE"
android:value="@string/application_id" />
</intent>
</Preference>
</PreferenceCategory>
<Preference android:title="@string/about"
android:key="about"
android:summary="@string/about_pixeldroid"
app:icon="@drawable/info_black_24dp">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="@string/application_id"
android:targetClass="org.pixeldroid.common.AboutActivity">
<extra android:name="buildVersion" android:value="@string/versionName" />
<extra android:name="appImage" android:value="mascot" />
<extra android:name="appImageWidth" android:value="508" />
<extra android:name="appImageTopMargin" android:value="-130" />
<extra android:name="appImageBottomMargin" android:value="-130" />
<extra android:name="appImageLeftMargin" android:value="0" />
<extra android:name="appImageRightMargin" android:value="0" />
<extra android:name="appName" android:value="@string/app_name" />
<extra android:name="aboutAppDescription" android:value="@string/license_info" />
<extra android:name="website" android:value="@string/project_website" />
<extra android:name="translatePlatformUrl" android:value="https://weblate.pixeldroid.org" />
<extra android:name="contributeForgeUrl" android:value="https://gitlab.shinice.net/pixeldroid/PixelDroid" />
</intent>
</Preference>
<PreferenceCategory app:title="@string/about">
<Preference android:title="@string/about"
android:key="about"
android:summary="@string/about_pixeldroid"
app:icon="@drawable/info_black_24dp">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="@string/application_id"
android:targetClass="org.pixeldroid.common.AboutActivity">
<extra android:name="buildVersion" android:value="@string/versionName" />
<extra android:name="appImage" android:value="mascot" />
<extra android:name="appImageWidth" android:value="508" />
<extra android:name="appImageTopMargin" android:value="-130" />
<extra android:name="appImageBottomMargin" android:value="-130" />
<extra android:name="appImageLeftMargin" android:value="0" />
<extra android:name="appImageRightMargin" android:value="0" />
<extra android:name="appName" android:value="@string/app_name" />
<extra android:name="aboutAppDescription" android:value="@string/license_info" />
<extra android:name="website" android:value="@string/project_website" />
<extra android:name="translatePlatformUrl" android:value="https://weblate.pixeldroid.org" />
<extra android:name="contributeForgeUrl" android:value="https://gitlab.shinice.net/pixeldroid/PixelDroid" />
</intent>
</Preference>
</PreferenceCategory>
</PreferenceScreen>

View File

@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.2.2'
classpath 'com.android.tools.build:gradle:8.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong

@ -1 +1 @@
Subproject commit 956bd5f88d6189009f2ba0a8cb2860a1bfee0ee6
Subproject commit 702d14fe701343958337efa1b4eb31f0250849f6

@ -1 +1 @@
Subproject commit 7c67b911930b4344a2917f2944493e08fdd04b57
Subproject commit 23d4d94b45a848f0c64a042985eb03d0acc2f18b