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' implementation 'com.google.android.material:material:1.11.0'
//Dagger (dependency injection) //Dagger (dependency injection)
implementation 'com.google.dagger:dagger:2.50' implementation 'com.google.dagger:dagger:2.51'
ksp 'com.google.dagger:dagger-compiler:2.50' ksp 'com.google.dagger:dagger-compiler:2.51'
implementation("com.google.dagger:hilt-android:2.50") implementation('com.google.dagger:hilt-android:2.51')
ksp "com.google.dagger:hilt-compiler:2.50" ksp 'com.google.dagger:hilt-compiler:2.51'
implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:retrofit:2.10.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.10.0'
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0' implementation 'com.squareup.retrofit2:adapter-rxjava3:2.10.0'
implementation 'io.reactivex.rxjava3:rxjava:3.1.8' implementation 'io.reactivex.rxjava3:rxjava:3.1.8'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.2' implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
implementation 'com.github.connyduck:sparkbutton:4.1.0' 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: ':scrambler')
implementation project(path: ':pixel_common') implementation project(path: ':pixel_common')

View File

@ -9,6 +9,7 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
@ -16,6 +17,8 @@ import androidx.activity.addCallback
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.PopupMenu
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@ -25,6 +28,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.paging.ExperimentalPagingApi import androidx.paging.ExperimentalPagingApi
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2 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.util.DrawerImageLoader
import com.mikepenz.materialdrawer.widget.AccountHeaderView import com.mikepenz.materialdrawer.widget.AccountHeaderView
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.json.JSONArray
import org.json.JSONObject
import org.ligi.tracedroid.sending.sendTraceDroidStackTracesIfExist import org.ligi.tracedroid.sending.sendTraceDroidStackTracesIfExist
import org.pixeldroid.app.databinding.ActivityMainBinding import org.pixeldroid.app.databinding.ActivityMainBinding
import org.pixeldroid.app.postCreation.camera.CameraFragment 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.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.db.updateUserInfoDb import org.pixeldroid.app.utils.db.updateUserInfoDb
import org.pixeldroid.app.utils.hasInternet 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.INSTANCE_NOTIFICATION_TAG
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.SHOW_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.NotificationsWorker.Companion.USER_NOTIFICATION_TAG
import org.pixeldroid.app.utils.notificationsWorker.enablePullNotifications import org.pixeldroid.app.utils.notificationsWorker.enablePullNotifications
import org.pixeldroid.app.utils.notificationsWorker.removeNotificationChannelsFromAccount import org.pixeldroid.app.utils.notificationsWorker.removeNotificationChannelsFromAccount
import org.pixeldroid.app.utils.toList
import java.time.Instant import java.time.Instant
@ -86,7 +95,6 @@ class MainActivity : BaseActivity() {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
@OptIn(ExperimentalPagingApi::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen().setOnExitAnimationListener { installSplashScreen().setOnExitAnimationListener {
it.remove() it.remove()
@ -112,24 +120,8 @@ class MainActivity : BaseActivity() {
sendTraceDroidStackTracesIfExist("contact@pixeldroid.org", this) sendTraceDroidStackTracesIfExist("contact@pixeldroid.org", this)
setupDrawer() setupDrawer()
val tabs: List<() -> Fragment> = listOf(
{ setupTabs()
PostFeedFragment<HomeStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", true) }
}
},
{ SearchDiscoverFragment() },
{ CameraFragment() },
{ NotificationsFragment() },
{
PostFeedFragment<PublicFeedStatusDatabaseEntity>()
.apply {
arguments = Bundle().apply { putBoolean("home", false) }
}
}
)
setupTabs(tabs)
val showNotification: Boolean = intent.getBooleanExtra(SHOW_NOTIFICATION_TAG, false) val showNotification: Boolean = intent.getBooleanExtra(SHOW_NOTIFICATION_TAG, false)
@ -396,29 +388,103 @@ class MainActivity : BaseActivity() {
touchSlopField.set(recyclerView, touchSlop*NestedScrollableHost.touchSlopModifier) 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.reduceDragSensitivity()
binding.viewPager.adapter = object : FragmentStateAdapter(this) { binding.viewPager.adapter = object : FragmentStateAdapter(this) {
override fun createFragment(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
return tab_array[position]() return tabArray[position]()
} }
override fun getItemCount(): Int { 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() { binding.viewPager.registerOnPageChangeCallback(object : OnPageChangeCallback() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
val selected = when(position){ val selected = when(position){
0 -> R.id.page_1 0 -> doAtPageId(R.id.page_1)
1 -> R.id.page_2 1 -> doAtPageId(R.id.page_2)
2 -> R.id.page_3 2 -> doAtPageId(R.id.page_3)
3 -> { 3 -> doAtPageId(R.id.page_4)
setNotificationBadge(false) 4 -> doAtPageId(R.id.page_5)
R.id.page_4
}
4 -> R.id.page_5
else -> null else -> null
} }
if (selected != 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.db.entities.InstanceDatabaseEntity
import org.pixeldroid.app.utils.fileExtension import org.pixeldroid.app.utils.fileExtension
import org.pixeldroid.app.utils.getMimeType 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.photoEdit.PhotoEditActivity
import org.pixeldroid.media_editor.videoEdit.VideoEditActivity import org.pixeldroid.media_editor.videoEdit.VideoEditActivity
import java.io.File import java.io.File
@ -47,7 +49,6 @@ import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
class PostCreationFragment : BaseFragment() { class PostCreationFragment : BaseFragment() {
private var binding: FragmentPostCreationBinding by bindingLifecycleAware() private var binding: FragmentPostCreationBinding by bindingLifecycleAware()
private val model: PostCreationViewModel by activityViewModels() private val model: PostCreationViewModel by activityViewModels()
@ -307,7 +308,7 @@ class PostCreationFragment : BaseFragment() {
ActivityResultContracts.StartActivityForResult()){ ActivityResultContracts.StartActivityForResult()){
result: ActivityResult? -> result: ActivityResult? ->
if (result?.resultCode == Activity.RESULT_OK && result.data != null) { 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!!) model.modifyAt(position, result.data!!)
?: Toast.makeText(requireActivity(), R.string.error_editing, Toast.LENGTH_SHORT).show() ?: Toast.makeText(requireActivity(), R.string.error_editing, Toast.LENGTH_SHORT).show()
} else if(result?.resultCode != Activity.RESULT_CANCELED){ } else if(result?.resultCode != Activity.RESULT_CANCELED){
@ -320,8 +321,8 @@ class PostCreationFragment : BaseFragment() {
requireActivity(), requireActivity(),
if (model.getPhotoData().value!![position].video) VideoEditActivity::class.java else PhotoEditActivity::class.java if (model.getPhotoData().value!![position].video) VideoEditActivity::class.java else PhotoEditActivity::class.java
) )
.putExtra(PhotoEditActivity.PICTURE_URI, model.getPhotoData().value!![position].imageUri) .putExtra(PICTURE_URI, model.getPhotoData().value!![position].imageUri)
.putExtra(PhotoEditActivity.PICTURE_POSITION, position) .putExtra(PICTURE_POSITION, position)
editResultContract.launch(intent) 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.di.PixelfedAPIHolder
import org.pixeldroid.app.utils.fileExtension import org.pixeldroid.app.utils.fileExtension
import org.pixeldroid.app.utils.getMimeType import org.pixeldroid.app.utils.getMimeType
import org.pixeldroid.media_editor.common.PICTURE_URI
import org.pixeldroid.media_editor.videoEdit.VideoEditActivity import org.pixeldroid.media_editor.videoEdit.VideoEditActivity
import retrofit2.HttpException import retrofit2.HttpException
import java.io.File import java.io.File
@ -299,7 +300,7 @@ class PostCreationViewModel @Inject constructor(
} }
} }
} else { } 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) val (imageSize, imageVideo) = getSizeAndVideoValidate(imageUri, position)
size = imageSize size = imageSize
video = imageVideo video = imageVideo

View File

@ -1,11 +1,18 @@
package org.pixeldroid.app.settings package org.pixeldroid.app.settings
import android.annotation.SuppressLint
import androidx.appcompat.app.AlertDialog
import android.app.Dialog import android.app.Dialog
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.XmlResourceParser import android.content.res.XmlResourceParser
import android.os.Build import android.os.Build
import android.os.Bundle 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.activity.addCallback
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat import androidx.core.os.LocaleListCompat
@ -14,12 +21,22 @@ import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager 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 com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.json.JSONArray
import org.json.JSONObject
import org.pixeldroid.app.MainActivity import org.pixeldroid.app.MainActivity
import org.pixeldroid.app.R import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.SettingsBinding 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.app.utils.setThemeFromPreferences
import org.pixeldroid.common.ThemedActivity
class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceChangeListener { class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
@ -103,6 +120,8 @@ class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceC
dialogFragment = ColorPreferenceDialog((preference as ColorPreference?)!!) dialogFragment = ColorPreferenceDialog((preference as ColorPreference?)!!)
} else if(preference.key == "language"){ } else if(preference.key == "language"){
dialogFragment = LanguageSettingFragment() dialogFragment = LanguageSettingFragment()
} else if (preference.key == "arrange_tabs") {
dialogFragment = ArrangeTabsFragment()
} }
if (dialogFragment != null) { if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0) dialogFragment.setTargetFragment(this, 0)
@ -132,6 +151,7 @@ class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceC
} }
class LanguageSettingFragment : DialogFragment() { class LanguageSettingFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val list: MutableList<String> = mutableListOf() val list: MutableList<String> = mutableListOf()
// IDE doesn't find it, but compiling works apparently? // IDE doesn't find it, but compiling works apparently?
@ -175,3 +195,131 @@ class LanguageSettingFragment : DialogFragment() {
}.create() }.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.net.Uri
import android.os.Build import android.os.Build
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.PopupMenu
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
@ -28,6 +30,8 @@ import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializer import com.google.gson.JsonSerializer
import okhttp3.HttpUrl import okhttp3.HttpUrl
import org.json.JSONArray
import org.json.JSONObject
import org.pixeldroid.app.R import org.pixeldroid.app.R
import java.time.Instant import java.time.Instant
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@ -192,4 +196,24 @@ fun <T> Fragment.bindingLifecycleAware(): ReadWriteProperty<Fragment, T> =
binding = value binding = value
this@bindingLifecycleAware.viewLifecycleOwner.lifecycle.addObserver(this) 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="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="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="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> </resources>

View File

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

View File

@ -6,7 +6,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { 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" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // 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