From 862e3b451b713c0cddd746e98738c19a7034fc10 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 6 Feb 2026 00:37:02 +0800 Subject: [PATCH] :art: Use feature based folder structure --- .../abuse_report_service.dart | 4 +- .../account/credits.dart | 10 +- .../account/credits.g.dart | 0 .../account/leveling.dart | 20 +- .../account/me/account_settings.dart | 26 +- .../account/me/account_settings.g.dart | 0 .../account/me/profile_update.dart | 22 +- .../account/me/settings_auth_factors.dart | 98 ++- .../account/me/settings_connections.dart | 211 +++-- .../account/me/settings_contacts.dart | 26 +- .../account/profile.dart | 52 +- .../account/profile.g.dart | 0 .../account/relationship.dart | 22 +- .../account/relationship.g.dart | 0 .../accounts_models}/abuse_report.dart | 0 .../abuse_report.freezed.dart | 0 .../accounts_models}/abuse_report.g.dart | 0 .../accounts_models}/abuse_report_type.dart | 0 .../accounts_models}/account.dart | 8 +- .../accounts_models}/account.freezed.dart | 0 .../accounts_models}/account.g.dart | 0 .../accounts_models}/badge.dart | 0 .../accounts_models}/fortune.dart | 0 .../accounts_models}/fortune.freezed.dart | 0 .../accounts_models}/fortune.g.dart | 0 .../accounts_models}/relationship.dart | 2 +- .../relationship.freezed.dart | 0 .../accounts_models}/relationship.g.dart | 0 .../accounts_pod.dart} | 10 +- .../accounts_screen.dart} | 28 +- .../account/account_devices.dart | 258 +++--- .../account/account_devices.g.dart | 0 .../account/account_name.dart | 8 +- .../account/account_nameplate.dart | 6 +- .../account/account_pfc.dart | 27 +- .../account/account_picker.dart | 6 +- .../account/account_picker.g.dart | 0 .../account/activity_presence.dart | 618 ++++++++++++++ .../account/activity_presence.g.dart | 0 .../accounts_widgets}/account/badge.dart | 4 +- .../account/event_calendar.dart | 47 +- .../account/event_calendar_content.dart | 101 ++- .../account/event_details_widget.dart | 6 +- .../account/fortune_graph.dart | 76 +- .../account/friends_overview.dart | 10 +- .../account/friends_overview.g.dart | 0 .../account/leveling_progress.dart | 0 .../account/restore_purchase_sheet.dart | 8 +- .../accounts_widgets}/account/status.dart | 158 ++-- .../accounts_widgets}/account/status.g.dart | 0 .../account/status_creation.dart | 32 +- .../account/stellar_program_tab.dart | 26 +- .../account/stellar_program_tab.g.dart | 8 +- .../activitypub/activitypub.dart | 0 .../activitypub/actor_list_item.dart | 2 +- .../activitypub/actor_profile.dart | 2 +- .../activitypub/user_list_item.dart | 2 +- lib/{widgets => accounts}/check_in.dart | 18 +- lib/{widgets => accounts}/check_in.g.dart | 0 lib/{pods => accounts}/event_calendar.dart | 4 +- lib/{pods => accounts}/event_calendar.g.dart | 0 lib/{widgets => accounts}/usage_overview.dart | 2 +- lib/{pods => }/activity/activity_rpc.dart | 4 +- lib/{pods => }/activity/activity_rpc.g.dart | 0 lib/{pods => }/activity/ipc_server.dart | 0 lib/{pods => }/activity/ipc_server.web.dart | 0 lib/{models => auth/auth_models}/auth.dart | 0 .../auth_models}/auth.freezed.dart | 0 lib/{models => auth/auth_models}/auth.g.dart | 0 lib/{screens => }/auth/captcha.config.dart | 2 +- lib/{screens => }/auth/captcha.config.g.dart | 0 lib/{screens => }/auth/captcha.dart | 0 lib/{screens => }/auth/captcha.native.dart | 4 +- lib/{screens => }/auth/captcha.web.dart | 6 +- lib/{screens => }/auth/create_account.dart | 2 +- .../auth/create_account_content.dart | 126 +-- .../auth/create_account_modal.dart | 2 +- lib/{screens => }/auth/login.dart | 2 +- lib/{screens => }/auth/login_content.dart | 143 ++-- lib/{screens => }/auth/login_modal.dart | 2 +- lib/{screens => }/auth/oidc.dart | 0 lib/{screens => }/auth/oidc.native.dart | 71 +- lib/{screens => }/auth/oidc.web.dart | 29 +- .../web_auth/web_auth_providers.dart | 0 .../web_auth/web_auth_server.dart | 2 +- lib/{models => chat/chat_models}/chat.dart | 6 +- .../chat_models}/chat.freezed.dart | 0 lib/{models => chat/chat_models}/chat.g.dart | 0 lib/{pods => }/chat/chat_online_count.dart | 6 +- lib/{pods => }/chat/chat_online_count.g.dart | 0 lib/{pods/chat => chat/chat_pod}/call.dart | 8 +- .../chat => chat/chat_pod}/call.freezed.dart | 0 lib/{pods/chat => chat/chat_pod}/call.g.dart | 0 lib/chat/chat_pod/chat_online_count.dart | 50 ++ lib/chat/chat_pod/chat_online_count.g.dart | 101 +++ .../chat => chat/chat_pod}/chat_room.dart | 16 +- .../chat => chat/chat_pod}/chat_room.g.dart | 0 .../chat_pod}/chat_subscribe.dart | 14 +- .../chat_pod}/chat_subscribe.g.dart | 0 .../chat => chat/chat_pod}/chat_summary.dart | 8 +- .../chat_pod}/chat_summary.g.dart | 0 lib/chat/chat_room.dart | 452 +++++++++++ lib/chat/chat_room.g.dart | 279 +++++++ lib/chat/chat_subscribe.dart | 294 +++++++ lib/chat/chat_subscribe.g.dart | 108 +++ lib/chat/chat_summary.dart | 163 ++++ lib/chat/chat_summary.g.dart | 108 +++ .../chat_widgets}/call_button.dart | 8 +- .../chat_widgets}/call_button.g.dart | 0 .../chat_widgets}/call_content.dart | 4 +- .../chat_widgets}/call_overlay.dart | 20 +- .../chat_widgets}/call_participant_card.dart | 13 +- .../chat_widgets}/call_participant_tile.dart | 12 +- .../chat_widgets/call_screen.dart} | 16 +- .../chat_widgets/chat_detail_screen.dart} | 38 +- .../chat_detail_screen.freezed.dart} | 2 +- .../chat_widgets/chat_detail_screen.g.dart} | 2 +- .../chat_widgets}/chat_input.dart | 40 +- lib/chat/chat_widgets/chat_invites_sheet.dart | 103 +++ .../chat_widgets}/chat_link_attachments.dart | 12 +- .../chat_widgets/chat_list_screen.dart} | 206 +---- .../chat_widgets/chat_room_form.dart} | 24 +- .../chat_widgets/chat_room_list_tile.dart | 78 ++ .../chat_widgets/chat_room_screen.dart} | 65 +- .../chat_widgets}/chat_room_widgets.dart | 4 +- .../chat_widgets/chat_search_screen.dart} | 274 +++---- .../chat_widgets}/message_content.dart | 6 +- .../chat_widgets}/message_indicators.dart | 2 +- .../chat_widgets}/message_item.dart | 290 ++++--- .../chat_widgets}/message_item_wrapper.dart | 8 +- .../chat_widgets}/message_list_tile.dart | 16 +- .../chat_widgets}/message_sender_info.dart | 14 +- .../chat_widgets}/public_room_preview.dart | 22 +- .../chat_widgets}/room_app_bar.dart | 6 +- .../chat_widgets}/room_message_list.dart | 8 +- .../chat_widgets}/room_overlays.dart | 4 +- .../chat_widgets}/room_selection_mode.dart | 0 .../hooks/use_room_file_picker.dart | 4 +- lib/{ => chat}/hooks/use_room_input.dart | 16 +- lib/{ => chat}/hooks/use_room_scroll.dart | 6 +- lib/{pods => }/chat/messages_notifier.dart | 28 +- lib/{pods => }/chat/messages_notifier.g.dart | 0 .../palette.dart} | 22 +- lib/{pods => core}/audio.dart | 2 +- lib/{pods => core}/config.dart | 2 +- lib/{pods => core}/config.freezed.dart | 0 lib/{pods => core}/config.g.dart | 0 lib/{widgets => core}/data_saving_gate.dart | 2 +- lib/{pods/message.dart => core/database.dart} | 11 +- lib/{widgets => core}/debug_sheet.dart | 57 +- lib/{pods => core}/lifecycle.dart | 0 lib/{pods => core}/link_preview.dart | 4 +- lib/{pods => core}/link_preview.g.dart | 0 lib/{ => core}/models/activity.dart | 2 +- lib/{ => core}/models/activity.freezed.dart | 0 lib/{ => core}/models/activity.g.dart | 0 lib/{ => core}/models/activitypub.dart | 0 .../models/activitypub.freezed.dart | 0 lib/{ => core}/models/activitypub.g.dart | 0 lib/{ => core}/models/route_item.dart | 0 lib/{ => core}/models/route_item.freezed.dart | 0 .../navigation/conditional_bottom_nav.dart | 4 +- lib/{pods => core}/network.dart | 2 +- lib/{pods => core}/network.g.dart | 0 lib/{pods => core}/notification.dart | 2 +- lib/{pods => core}/notification.g.dart | 0 .../services/activitypub_service.dart | 4 +- .../services/analytics_service.dart | 0 lib/{ => core}/services/color.dart | 0 lib/{ => core}/services/color_extraction.dart | 0 lib/{ => core}/services/event_bus.dart | 0 .../file.dart => core/services/image.dart} | 6 +- lib/{ => core}/services/notify.dart | 0 lib/{ => core}/services/notify.universal.dart | 10 +- lib/{ => core}/services/notify.windows.dart | 10 +- lib/{ => core}/services/quick_actions.dart | 2 +- lib/{ => core}/services/responsive.dart | 0 lib/{ => core}/services/sharing_intent.dart | 2 +- lib/{ => core}/services/time.dart | 0 lib/{ => core}/services/timezone.dart | 0 lib/{ => core}/services/timezone/native.dart | 0 lib/{ => core}/services/timezone/web.dart | 0 lib/{ => core}/services/tour.dart | 2 +- lib/{ => core}/services/tour.freezed.dart | 0 lib/{ => core}/services/tour.g.dart | 0 lib/{ => core}/services/udid.dart | 0 lib/{ => core}/services/udid.native.dart | 0 lib/{ => core}/services/udid.web.dart | 0 lib/{ => core}/services/update_service.dart | 4 +- .../services/widget_sync_service.dart | 0 lib/{pods => core}/theme.dart | 2 +- lib/{pods => core}/theme.g.dart | 0 .../tour/techincal_review_intro.dart | 2 +- lib/{widgets => core}/tour/tour.dart | 2 +- lib/{pods => core}/translate.dart | 2 +- lib/{pods => core}/translate.freezed.dart | 0 lib/{pods => core}/translate.g.dart | 0 lib/{ => core}/utils/abuse_report_utils.dart | 0 lib/{ => core}/utils/activity_utils.dart | 2 +- lib/{ => core}/utils/file_icon_utils.dart | 5 +- lib/{ => core}/utils/format.dart | 0 lib/{ => core}/utils/mapping.dart | 0 lib/{ => core}/utils/share_utils.dart | 12 +- lib/{ => core}/utils/text.dart | 0 lib/{pods => core}/websocket.dart | 4 +- lib/{pods => core}/websocket.freezed.dart | 0 lib/{pods => core}/websocket.g.dart | 0 .../widgets/content/attachment_preview.dart | 18 +- lib/{ => core}/widgets/content/audio.dart | 6 +- .../content/cloud_file_collection.dart | 10 +- .../widgets/content/cloud_file_lightbox.dart | 16 +- .../widgets/content/cloud_file_picker.dart | 88 +- .../widgets/content/embed/embed_list.dart | 8 +- .../widgets/content/embed/link.dart | 4 +- .../widgets/content/exif_info_overlay.dart | 2 +- .../widgets/content/file_action_button.dart | 0 .../widgets/content/file_info_sheet.dart | 8 +- .../widgets/content/file_viewer_contents.dart | 22 +- lib/{ => core}/widgets/content/image.dart | 4 +- .../content/image_control_overlay.dart | 0 lib/{ => core}/widgets/content/markdown.dart | 20 +- .../widgets/content/markdown_latex.dart | 0 .../widgets/content/network_status_sheet.dart | 8 +- .../widgets/content/profile_decoration.dart | 0 .../content/profile_decoration.freezed.dart | 0 lib/{ => core}/widgets/content/sensitive.dart | 0 lib/{ => core}/widgets/content/sheet.dart | 0 lib/{ => core}/widgets/content/video.dart | 0 .../widgets/content/video.native.dart | 4 +- lib/{ => core}/widgets/content/video.web.dart | 0 lib/{ => core}/widgets/payment/README.md | 0 .../widgets/payment/payment_overlay.dart | 8 +- lib/{ => core}/widgets/share/share_sheet.dart | 24 +- .../widgets/shared/upload_menu.dart | 0 lib/{screens => creators}/creators/hub.dart | 36 +- lib/{screens => creators}/creators/hub.g.dart | 0 .../creators/poll/poll_list.dart | 18 +- .../creators/poll/poll_list.g.dart | 0 .../creators/posts/post_manage_list.dart | 6 +- .../creators/publishers_form.dart | 22 +- .../creators/publishers_form.g.dart | 0 .../creators/sites/site_detail.dart | 102 ++- .../creators/sites/site_detail.g.dart | 0 .../creators/sites/site_edit.dart | 16 +- .../creators/sites/site_list.dart | 14 +- .../sites/widgets/site_config_form.dart | 2 +- .../creators/stickers/pack_detail.dart | 16 +- .../stickers/pack_detail.freezed.dart | 0 .../creators/stickers/pack_detail.g.dart | 0 .../creators/stickers/stickers.dart | 24 +- .../creators/stickers/stickers.g.dart | 0 .../creators/webfeed/webfeed_edit.dart | 8 +- .../creators/webfeed/webfeed_list.dart | 36 +- .../publication_site.dart | 0 .../publication_site.freezed.dart | 0 .../publication_site.g.dart | 0 lib/{database => data}/database.native.dart | 2 +- lib/{database => data}/database.web.dart | 2 +- lib/{database => data}/draft.dart | 0 lib/{database => data}/drift_db.dart | 12 +- lib/{database => data}/drift_db.g.dart | 0 lib/{database => data}/drift_db.steps.dart | 0 lib/{database => data}/message.dart | 4 +- .../developers/app_detail.dart | 12 +- .../developers/app_secrets.dart | 12 +- .../developers/app_secrets.g.dart | 0 .../developers/apps.dart | 18 +- .../developers/apps.g.dart | 0 .../developers/bot_detail.dart | 12 +- .../developers/bot_keys.dart | 12 +- .../developers/bot_keys.g.dart | 0 .../developers/bots.dart | 18 +- .../developers/bots.g.dart | 0 .../developers/edit_app.dart | 22 +- .../developers/edit_app.g.dart | 0 .../developers/edit_bot.dart | 18 +- .../developers/edit_bot.g.dart | 0 .../developers/edit_project.dart | 103 ++- .../developers/edit_project.g.dart | 0 .../developers/hub.dart | 24 +- .../developers/hub.g.dart | 0 .../developers/new_app.dart | 2 +- .../developers/new_bot.dart | 2 +- .../developers/new_project.dart | 3 +- .../developers/project_detail_view.dart | 8 +- .../developers_models}/bot.dart | 4 +- .../developers_models}/bot.freezed.dart | 0 .../developers_models}/bot.g.dart | 0 .../developers_models}/bot_key.dart | 0 .../developers_models}/bot_key.freezed.dart | 0 .../developers_models}/bot_key.g.dart | 0 .../developers_models}/custom_app.dart | 4 +- .../custom_app.freezed.dart | 0 .../developers_models}/custom_app.g.dart | 0 .../developers_models}/custom_app_secret.dart | 0 .../custom_app_secret.freezed.dart | 0 .../custom_app_secret.g.dart | 0 .../developers_models}/dev_project.dart | 0 .../developers_models}/developer.dart | 2 +- .../developers_models}/developer.freezed.dart | 0 .../developers_models}/developer.g.dart | 0 .../discovery/article_detail.dart | 39 +- .../discovery/article_pod.dart} | 6 +- .../discovery/articles.dart | 12 +- .../discovery/articles.freezed.dart | 0 .../discovery/articles.g.dart | 0 .../discovery/feeds/feed_detail.dart | 14 +- .../discovery/feeds/feed_detail.g.dart | 0 .../discovery/feeds/feed_marketplace.dart | 10 +- .../discovery/realms.dart | 4 +- .../discovery_models}/auto_completion.dart | 0 .../auto_completion.freezed.dart | 0 .../discovery_models}/auto_completion.g.dart | 0 .../autocomplete_response.dart | 0 .../autocomplete_response.freezed.dart | 0 .../autocomplete_response.g.dart | 0 .../discovery_models}/site_file.dart | 0 .../discovery_models}/site_file.freezed.dart | 0 .../discovery_models}/site_file.g.dart | 0 .../discovery_models}/webfeed.dart | 2 +- .../discovery_models}/webfeed.freezed.dart | 0 .../discovery_models}/webfeed.g.dart | 0 .../discovery_service.dart} | 4 +- lib/{screens => discovery}/explore.dart | 50 +- lib/{screens => discovery}/search.dart | 34 +- .../web_article_card.dart | 4 +- lib/{pods => discovery}/webfeed.dart | 4 +- lib/{pods => drive}/drive/file_list.dart | 8 +- lib/{pods => drive}/drive/file_list.g.dart | 0 lib/{pods => drive}/drive/file_pool.dart | 6 +- lib/{pods => drive}/drive/upload_tasks.dart | 238 +++--- .../drive_models}/drive_task.dart | 2 +- .../drive_models}/drive_task.freezed.dart | 0 .../drive_models}/drive_task.g.dart | 0 lib/{models => drive/drive_models}/file.dart | 2 +- .../drive_models}/file.freezed.dart | 0 .../drive_models}/file.g.dart | 0 .../drive_models}/file_list_item.dart | 2 +- .../drive_models}/file_list_item.freezed.dart | 0 .../drive_models}/file_pool.dart | 0 .../drive_models}/file_pool.freezed.dart | 0 .../drive_models}/file_pool.g.dart | 0 .../drive_models}/folder.dart | 0 .../drive_models}/folder.freezed.dart | 0 .../drive_models}/folder.g.dart | 0 .../drive_service.dart} | 127 ++- .../drive_widgets}/cloud_files.dart | 19 +- .../drive_widgets}/file_list_view.dart | 26 +- lib/{screens => drive}/files/file_detail.dart | 14 +- lib/{screens => drive}/files/file_list.dart | 18 +- lib/{services => fitness}/fitness_data.dart | 0 .../fitness_screen.dart} | 14 +- .../fitness_service.dart | 2 +- lib/{screens => lotteries}/lottery.dart | 14 +- lib/{screens => lotteries}/lottery.g.dart | 4 +- lib/main.dart | 26 +- .../notification.dart | 18 +- .../notification.g.dart | 0 .../notification_item.dart | 4 +- .../notification_overlay.dart | 6 +- .../notification_tile.dart | 6 +- .../pagination.dart} | 0 lib/pods/database.dart | 11 - lib/{screens => polls}/poll/poll_editor.dart | 523 ++++++------ .../polls_widgets}/poll/poll_feedback.dart | 24 +- .../poll/poll_stats_widget.dart | 2 +- .../polls_widgets}/poll/poll_submit.dart | 129 ++- lib/{widgets => posts}/activity_heatmap.dart | 4 +- lib/{screens => }/posts/compose.dart | 4 +- lib/{screens => }/posts/compose.freezed.dart | 0 lib/{screens => }/posts/compose.g.dart | 0 lib/{screens => }/posts/compose_article.dart | 34 +- .../compose_storage_db.dart | 6 +- .../compose_storage_db.g.dart | 0 lib/{pods => posts}/post/post_categories.dart | 8 +- lib/{pods => posts}/post/post_list.dart | 6 +- .../post/post_list.freezed.dart | 0 lib/posts/posts/compose.dart | 22 + lib/posts/posts/compose.freezed.dart | 343 ++++++++ lib/posts/posts/compose.g.dart | 39 + lib/posts/posts/compose_article.dart | 467 +++++++++++ .../posts/post_categories_list.dart | 6 +- .../posts/post_category_detail.dart | 15 +- .../posts/post_category_detail.g.dart | 0 lib/{screens => posts}/posts/post_detail.dart | 44 +- .../posts/post_detail.g.dart | 0 .../posts/publisher_profile.dart | 42 +- .../posts/publisher_profile.g.dart | 0 lib/{models => posts/posts_models}/embed.dart | 0 .../posts_models}/embed.freezed.dart | 0 .../posts_models}/embed.g.dart | 0 .../posts_models}/heatmap.dart | 0 .../posts_models}/heatmap.freezed.dart | 0 .../posts_models}/heatmap.g.dart | 0 lib/{models => posts/posts_models}/poll.dart | 4 +- .../posts_models}/poll.freezed.dart | 0 .../posts_models}/poll.g.dart | 0 lib/{models => posts/posts_models}/post.dart | 14 +- .../posts_models}/post.freezed.dart | 0 .../posts_models}/post.g.dart | 0 .../posts_models}/post_category.dart | 6 +- .../posts_models}/post_category.freezed.dart | 0 .../posts_models}/post_category.g.dart | 0 .../posts_models}/post_tag.dart | 2 +- .../posts_models}/post_tag.freezed.dart | 0 .../posts_models}/post_tag.g.dart | 0 .../posts_models}/publisher.dart | 4 +- .../posts_models}/publisher.freezed.dart | 0 .../posts_models}/publisher.g.dart | 0 .../timeline.dart => posts/posts_pod.dart} | 12 +- .../posts_widgets}/compose_sheet.dart | 20 +- .../post/article_sidebar_panel.dart | 0 .../post/compose_attachments.dart | 101 ++- .../posts_widgets}/post/compose_card.dart | 32 +- .../posts_widgets}/post/compose_dialog.dart | 98 ++- .../post/compose_embed_sheet.dart | 87 +- .../post/compose_form_fields.dart | 18 +- .../posts_widgets}/post/compose_fund.dart | 12 +- .../post/compose_info_banner.dart | 4 +- .../post/compose_link_attachments.dart | 12 +- .../posts_widgets}/post/compose_poll.dart | 113 ++- .../posts_widgets}/post/compose_recorder.dart | 17 +- .../post/compose_settings_sheet.dart | 18 +- .../posts_widgets}/post/compose_shared.dart | 34 +- .../posts_widgets/post/compose_sheet.dart | 328 ++++++++ .../post/compose_state_utils.dart | 25 +- .../posts_widgets}/post/compose_toolbar.dart | 12 +- .../posts_widgets}/post/draft_manager.dart | 20 +- .../post/embed_view_renderer.dart | 2 +- .../post/filters/post_filter.dart | 2 +- .../filters/post_subscription_filter.dart | 8 +- .../filters/post_subscription_filter.g.dart | 0 .../post/post_award_history_sheet.dart | 10 +- .../posts_widgets}/post/post_award_sheet.dart | 14 +- .../posts_widgets}/post/post_featured.dart | 8 +- .../posts_widgets}/post/post_featured.g.dart | 0 .../posts_widgets}/post/post_item.dart | 38 +- .../post/post_item_creator.dart | 14 +- .../post/post_item_screenshot.dart | 12 +- .../post/post_item_skeleton.dart | 0 .../posts_widgets}/post/post_list.dart | 12 +- .../posts_widgets}/post/post_pin_sheet.dart | 37 +- .../posts_widgets}/post/post_quick_reply.dart | 20 +- .../post/post_reaction_sheet.dart | 24 +- .../post/post_reaction_sheet.freezed.dart | 0 .../posts_widgets}/post/post_replies.dart | 12 +- .../post/post_replies_sheet.dart | 37 +- .../posts_widgets}/post/post_shared.dart | 26 +- .../posts_widgets}/post/post_shared.g.dart | 0 .../posts_widgets}/post/post_shuffle.dart | 6 +- .../posts_widgets}/post/publishers_modal.dart | 4 +- .../publisher/publisher_card.dart | 4 +- lib/posts/publisher_profile.dart | 756 ++++++++++++++++++ lib/posts/publisher_profile.g.dart | 388 +++++++++ .../realm/realm_detail.dart | 42 +- lib/{screens => realms}/realm/realm_form.dart | 18 +- lib/{screens => realms}/realm/realms.dart | 25 +- lib/{screens => realms}/realm/realms.g.dart | 0 .../realms_models}/realm.dart | 4 +- .../realms_models}/realm.freezed.dart | 0 .../realms_models}/realm.g.dart | 0 .../realms_widgets}/realm/realm_card.dart | 4 +- .../realms_widgets}/realm/realm_list.dart | 10 +- .../realm/realm_list_tile.dart | 4 +- .../realm/realm_selection_dropdown.dart | 4 +- .../realms_widgets}/realm/realm_tile.dart | 4 +- .../reports/report_detail.dart | 8 +- .../reports/report_list.dart | 27 +- .../safety/abuse_report_helper.dart | 11 +- .../safety/abuse_report_sheet.dart | 56 +- lib/route.dart | 114 +-- lib/screens/activitypub/search.dart | 180 ----- lib/screens/chat/public_room_preview.dart | 215 ----- lib/services/file_download.dart | 128 --- lib/{screens => settings}/about.dart | 6 +- lib/{screens => settings}/dashboard/dash.dart | 43 +- .../dashboard/dash_customize.dart | 6 +- lib/{screens => settings}/settings.dart | 16 +- .../tabs.dart => settings/tabs_screen.dart} | 12 +- lib/{screens => settings}/tray_manager.dart | 0 lib/{ => shared}/widgets/alert.dart | 6 +- .../widgets/app_notification.dart | 4 +- lib/{ => shared}/widgets/app_scaffold.dart | 22 +- lib/{ => shared}/widgets/app_wrapper.dart | 32 +- .../widgets/attachment_uploader.dart | 12 +- lib/{ => shared}/widgets/empty_state.dart | 0 .../widgets/extended_refresh_indicator.dart | 0 .../sites => shared/widgets}/info_row.dart | 0 .../widgets/loading_indicator.dart | 0 .../widgets}/pagination_list.dart | 6 +- lib/{ => shared}/widgets/response.dart | 2 +- .../widgets}/responsive_sidebar.dart | 26 +- lib/{ => shared}/widgets/task_overlay.dart | 6 +- lib/{pods => sites}/site_files.dart | 4 +- lib/{pods => sites}/site_files.g.dart | 0 lib/{pods => sites}/site_pages.dart | 4 +- lib/{pods => sites}/site_pages.g.dart | 0 .../sites_widgets}/file_item.dart | 14 +- .../file_management_action_section.dart | 8 +- .../file_management_section.dart | 10 +- .../sites_widgets}/file_upload_dialog.dart | 90 +-- .../sites_widgets}/page_form.dart | 8 +- .../sites_widgets}/page_item.dart | 8 +- .../sites_widgets}/pages_section.dart | 39 +- .../sites_widgets}/site_action_menu.dart | 10 +- .../sites_widgets}/site_detail_content.dart | 19 +- .../sites_widgets}/site_info_card.dart | 13 +- .../stickers/pack_detail.dart | 12 +- .../stickers/pack_detail.g.dart | 0 .../stickers/sticker_marketplace.dart | 12 +- .../stickers/sticker_marketplace.freezed.dart | 0 .../stickers_models}/sticker.dart | 4 +- .../stickers_models}/sticker.freezed.dart | 0 .../stickers_models}/sticker.g.dart | 0 .../stickers/sticker_picker.dart | 8 +- .../stickers/sticker_picker.g.dart | 0 lib/{models => thought}/thought.dart | 2 +- lib/{models => thought}/thought.freezed.dart | 0 lib/{models => thought}/thought.g.dart | 0 lib/{screens => thought}/thought/think.dart | 145 ++-- lib/{screens => thought}/thought/think.g.dart | 0 .../thought/think_sheet.dart | 10 +- .../thought/function_calls_section.dart | 0 .../thought/proposals_section.dart | 0 .../thought/reasoning_section.dart | 0 .../thought/thought_content.dart | 6 +- .../thought/thought_header.dart | 0 .../thought/thought_proposal.dart | 0 .../thought/thought_sequence_list.dart | 12 +- .../thought/thought_shared.dart | 26 +- .../thought_widgets}/thought/token_info.dart | 2 +- lib/{screens => wallet}/wallet.dart | 32 +- lib/{screens => wallet}/wallet.g.dart | 6 +- .../wallet_models}/wallet.dart | 2 +- .../wallet_models}/wallet.freezed.dart | 0 .../wallet_models}/wallet.g.dart | 0 .../wallet_widgets}/wallet/fund_envelope.dart | 8 +- .../wallet/fund_envelope.g.dart | 2 +- lib/widgets/account/activity_presence.dart | 644 --------------- 539 files changed, 8406 insertions(+), 5056 deletions(-) rename lib/{services => accounts}/abuse_report_service.dart (86%) rename lib/{screens => accounts}/account/credits.dart (95%) rename lib/{screens => accounts}/account/credits.g.dart (100%) rename lib/{screens => accounts}/account/leveling.dart (93%) rename lib/{screens => accounts}/account/me/account_settings.dart (96%) rename lib/{screens => accounts}/account/me/account_settings.g.dart (100%) rename lib/{screens => accounts}/account/me/profile_update.dart (98%) rename lib/{screens => accounts}/account/me/settings_auth_factors.dart (83%) rename lib/{screens => accounts}/account/me/settings_connections.dart (63%) rename lib/{screens => accounts}/account/me/settings_contacts.dart (94%) rename lib/{screens => accounts}/account/profile.dart (96%) rename lib/{screens => accounts}/account/profile.g.dart (100%) rename lib/{screens => accounts}/account/relationship.dart (96%) rename lib/{screens => accounts}/account/relationship.g.dart (100%) rename lib/{models => accounts/accounts_models}/abuse_report.dart (100%) rename lib/{models => accounts/accounts_models}/abuse_report.freezed.dart (100%) rename lib/{models => accounts/accounts_models}/abuse_report.g.dart (100%) rename lib/{models => accounts/accounts_models}/abuse_report_type.dart (100%) rename lib/{models => accounts/accounts_models}/account.dart (97%) rename lib/{models => accounts/accounts_models}/account.freezed.dart (100%) rename lib/{models => accounts/accounts_models}/account.g.dart (100%) rename lib/{models => accounts/accounts_models}/badge.dart (100%) rename lib/{models => accounts/accounts_models}/fortune.dart (100%) rename lib/{models => accounts/accounts_models}/fortune.freezed.dart (100%) rename lib/{models => accounts/accounts_models}/fortune.g.dart (100%) rename lib/{models => accounts/accounts_models}/relationship.dart (91%) rename lib/{models => accounts/accounts_models}/relationship.freezed.dart (100%) rename lib/{models => accounts/accounts_models}/relationship.g.dart (100%) rename lib/{pods/userinfo.dart => accounts/accounts_pod.dart} (91%) rename lib/{screens/account.dart => accounts/accounts_screen.dart} (96%) rename lib/{widgets => accounts/accounts_widgets}/account/account_devices.dart (56%) rename lib/{widgets => accounts/accounts_widgets}/account/account_devices.g.dart (100%) rename lib/{widgets => accounts/accounts_widgets}/account/account_name.dart (98%) rename lib/{widgets => accounts/accounts_widgets}/account/account_nameplate.dart (97%) rename lib/{widgets => accounts/accounts_widgets}/account/account_pfc.dart (92%) rename lib/{widgets => accounts/accounts_widgets}/account/account_picker.dart (95%) rename lib/{widgets => accounts/accounts_widgets}/account/account_picker.g.dart (100%) create mode 100644 lib/accounts/accounts_widgets/account/activity_presence.dart rename lib/{widgets => accounts/accounts_widgets}/account/activity_presence.g.dart (100%) rename lib/{widgets => accounts/accounts_widgets}/account/badge.dart (90%) rename lib/{widgets => accounts/accounts_widgets}/account/event_calendar.dart (83%) rename lib/{widgets => accounts/accounts_widgets}/account/event_calendar_content.dart (59%) rename lib/{widgets => accounts/accounts_widgets}/account/event_details_widget.dart (96%) rename lib/{widgets => accounts/accounts_widgets}/account/fortune_graph.dart (84%) rename lib/{widgets => accounts/accounts_widgets}/account/friends_overview.dart (97%) rename lib/{widgets => accounts/accounts_widgets}/account/friends_overview.g.dart (100%) rename lib/{widgets => accounts/accounts_widgets}/account/leveling_progress.dart (100%) rename lib/{widgets => accounts/accounts_widgets}/account/restore_purchase_sheet.dart (95%) rename lib/{widgets => accounts/accounts_widgets}/account/status.dart (60%) rename lib/{widgets => accounts/accounts_widgets}/account/status.g.dart (100%) rename lib/{widgets => accounts/accounts_widgets}/account/status_creation.dart (91%) rename lib/{widgets => accounts/accounts_widgets}/account/stellar_program_tab.dart (98%) rename lib/{widgets => accounts/accounts_widgets}/account/stellar_program_tab.g.dart (96%) rename lib/{widgets => accounts/accounts_widgets}/activitypub/activitypub.dart (100%) rename lib/{widgets => accounts/accounts_widgets}/activitypub/actor_list_item.dart (98%) rename lib/{widgets => accounts/accounts_widgets}/activitypub/actor_profile.dart (97%) rename lib/{widgets => accounts/accounts_widgets}/activitypub/user_list_item.dart (98%) rename lib/{widgets => accounts}/check_in.dart (95%) rename lib/{widgets => accounts}/check_in.g.dart (100%) rename lib/{pods => accounts}/event_calendar.dart (93%) rename lib/{pods => accounts}/event_calendar.g.dart (100%) rename lib/{widgets => accounts}/usage_overview.dart (99%) rename lib/{pods => }/activity/activity_rpc.dart (99%) rename lib/{pods => }/activity/activity_rpc.g.dart (100%) rename lib/{pods => }/activity/ipc_server.dart (100%) rename lib/{pods => }/activity/ipc_server.web.dart (100%) rename lib/{models => auth/auth_models}/auth.dart (100%) rename lib/{models => auth/auth_models}/auth.freezed.dart (100%) rename lib/{models => auth/auth_models}/auth.g.dart (100%) rename lib/{screens => }/auth/captcha.config.dart (87%) rename lib/{screens => }/auth/captcha.config.g.dart (100%) rename lib/{screens => }/auth/captcha.dart (100%) rename lib/{screens => }/auth/captcha.native.dart (92%) rename lib/{screens => }/auth/captcha.web.dart (93%) rename lib/{screens => }/auth/create_account.dart (90%) rename lib/{screens => }/auth/create_account_content.dart (90%) rename lib/{screens => }/auth/create_account_modal.dart (89%) rename lib/{screens => }/auth/login.dart (94%) rename lib/{screens => }/auth/login_content.dart (87%) rename lib/{screens => }/auth/login_modal.dart (88%) rename lib/{screens => }/auth/oidc.dart (100%) rename lib/{screens => }/auth/oidc.native.dart (76%) rename lib/{screens => }/auth/oidc.web.dart (74%) rename lib/{pods => auth}/web_auth/web_auth_providers.dart (100%) rename lib/{pods => auth}/web_auth/web_auth_server.dart (99%) rename lib/{models => chat/chat_models}/chat.dart (96%) rename lib/{models => chat/chat_models}/chat.freezed.dart (100%) rename lib/{models => chat/chat_models}/chat.g.dart (100%) rename lib/{pods => }/chat/chat_online_count.dart (90%) rename lib/{pods => }/chat/chat_online_count.g.dart (100%) rename lib/{pods/chat => chat/chat_pod}/call.dart (98%) rename lib/{pods/chat => chat/chat_pod}/call.freezed.dart (100%) rename lib/{pods/chat => chat/chat_pod}/call.g.dart (100%) create mode 100644 lib/chat/chat_pod/chat_online_count.dart create mode 100644 lib/chat/chat_pod/chat_online_count.g.dart rename lib/{pods/chat => chat/chat_pod}/chat_room.dart (97%) rename lib/{pods/chat => chat/chat_pod}/chat_room.g.dart (100%) rename lib/{pods/chat => chat/chat_pod}/chat_subscribe.dart (96%) rename lib/{pods/chat => chat/chat_pod}/chat_subscribe.g.dart (100%) rename lib/{pods/chat => chat/chat_pod}/chat_summary.dart (95%) rename lib/{pods/chat => chat/chat_pod}/chat_summary.g.dart (100%) create mode 100644 lib/chat/chat_room.dart create mode 100644 lib/chat/chat_room.g.dart create mode 100644 lib/chat/chat_subscribe.dart create mode 100644 lib/chat/chat_subscribe.g.dart create mode 100644 lib/chat/chat_summary.dart create mode 100644 lib/chat/chat_summary.g.dart rename lib/{widgets/chat => chat/chat_widgets}/call_button.dart (94%) rename lib/{widgets/chat => chat/chat_widgets}/call_button.g.dart (100%) rename lib/{widgets/chat => chat/chat_widgets}/call_content.dart (97%) rename lib/{widgets/chat => chat/chat_widgets}/call_overlay.dart (97%) rename lib/{widgets/chat => chat/chat_widgets}/call_participant_card.dart (92%) rename lib/{widgets/chat => chat/chat_widgets}/call_participant_tile.dart (96%) rename lib/{screens/chat/call.dart => chat/chat_widgets/call_screen.dart} (90%) rename lib/{screens/chat/room_detail.dart => chat/chat_widgets/chat_detail_screen.dart} (96%) rename lib/{screens/chat/room_detail.freezed.dart => chat/chat_widgets/chat_detail_screen.freezed.dart} (99%) rename lib/{screens/chat/room_detail.g.dart => chat/chat_widgets/chat_detail_screen.g.dart} (98%) rename lib/{widgets/chat => chat/chat_widgets}/chat_input.dart (97%) create mode 100644 lib/chat/chat_widgets/chat_invites_sheet.dart rename lib/{widgets/chat => chat/chat_widgets}/chat_link_attachments.dart (95%) rename lib/{screens/chat/chat.dart => chat/chat_widgets/chat_list_screen.dart} (79%) rename lib/{screens/chat/chat_form.dart => chat/chat_widgets/chat_room_form.dart} (94%) create mode 100644 lib/chat/chat_widgets/chat_room_list_tile.dart rename lib/{screens/chat/room.dart => chat/chat_widgets/chat_room_screen.dart} (90%) rename lib/{widgets => chat/chat_widgets}/chat_room_widgets.dart (98%) rename lib/{screens/chat/search_messages.dart => chat/chat_widgets/chat_search_screen.dart} (65%) rename lib/{widgets/chat => chat/chat_widgets}/message_content.dart (97%) rename lib/{widgets/chat => chat/chat_widgets}/message_indicators.dart (98%) rename lib/{widgets/chat => chat/chat_widgets}/message_item.dart (85%) rename lib/{screens/chat/widgets => chat/chat_widgets}/message_item_wrapper.dart (96%) rename lib/{widgets/chat => chat/chat_widgets}/message_list_tile.dart (84%) rename lib/{widgets/chat => chat/chat_widgets}/message_sender_info.dart (90%) rename lib/{widgets/chat => chat/chat_widgets}/public_room_preview.dart (92%) rename lib/{widgets/chat => chat/chat_widgets}/room_app_bar.dart (95%) rename lib/{widgets/chat => chat/chat_widgets}/room_message_list.dart (96%) rename lib/{widgets/chat => chat/chat_widgets}/room_overlays.dart (96%) rename lib/{widgets/chat => chat/chat_widgets}/room_selection_mode.dart (100%) rename lib/{ => chat}/hooks/use_room_file_picker.dart (96%) rename lib/{ => chat}/hooks/use_room_input.dart (94%) rename lib/{ => chat}/hooks/use_room_scroll.dart (96%) rename lib/{pods => }/chat/messages_notifier.dart (98%) rename lib/{pods => }/chat/messages_notifier.g.dart (100%) rename lib/{widgets/cmp/pattle.dart => command_palette/palette.dart} (97%) rename lib/{pods => core}/audio.dart (97%) rename lib/{pods => core}/config.dart (99%) rename lib/{pods => core}/config.freezed.dart (100%) rename lib/{pods => core}/config.g.dart (100%) rename lib/{widgets => core}/data_saving_gate.dart (93%) rename lib/{pods/message.dart => core/database.dart} (71%) rename lib/{widgets => core}/debug_sheet.dart (86%) rename lib/{pods => core}/lifecycle.dart (100%) rename lib/{pods => core}/link_preview.dart (86%) rename lib/{pods => core}/link_preview.g.dart (100%) rename lib/{ => core}/models/activity.dart (97%) rename lib/{ => core}/models/activity.freezed.dart (100%) rename lib/{ => core}/models/activity.g.dart (100%) rename lib/{ => core}/models/activitypub.dart (100%) rename lib/{ => core}/models/activitypub.freezed.dart (100%) rename lib/{ => core}/models/activitypub.g.dart (100%) rename lib/{ => core}/models/route_item.dart (100%) rename lib/{ => core}/models/route_item.freezed.dart (100%) rename lib/{widgets => core}/navigation/conditional_bottom_nav.dart (89%) rename lib/{pods => core}/network.dart (99%) rename lib/{pods => core}/network.g.dart (100%) rename lib/{pods => core}/notification.dart (97%) rename lib/{pods => core}/notification.g.dart (100%) rename lib/{ => core}/services/activitypub_service.dart (96%) rename lib/{ => core}/services/analytics_service.dart (100%) rename lib/{ => core}/services/color.dart (100%) rename lib/{ => core}/services/color_extraction.dart (100%) rename lib/{ => core}/services/event_bus.dart (100%) rename lib/{services/file.dart => core/services/image.dart} (90%) rename lib/{ => core}/services/notify.dart (100%) rename lib/{ => core}/services/notify.universal.dart (96%) rename lib/{ => core}/services/notify.windows.dart (95%) rename lib/{ => core}/services/quick_actions.dart (97%) rename lib/{ => core}/services/responsive.dart (100%) rename lib/{ => core}/services/sharing_intent.dart (98%) rename lib/{ => core}/services/time.dart (100%) rename lib/{ => core}/services/timezone.dart (100%) rename lib/{ => core}/services/timezone/native.dart (100%) rename lib/{ => core}/services/timezone/web.dart (100%) rename lib/{ => core}/services/tour.dart (97%) rename lib/{ => core}/services/tour.freezed.dart (100%) rename lib/{ => core}/services/tour.g.dart (100%) rename lib/{ => core}/services/udid.dart (100%) rename lib/{ => core}/services/udid.native.dart (100%) rename lib/{ => core}/services/udid.web.dart (100%) rename lib/{ => core}/services/update_service.dart (99%) rename lib/{ => core}/services/widget_sync_service.dart (100%) rename lib/{pods => core}/theme.dart (98%) rename lib/{pods => core}/theme.g.dart (100%) rename lib/{widgets => core}/tour/techincal_review_intro.dart (96%) rename lib/{widgets => core}/tour/tour.dart (95%) rename lib/{pods => core}/translate.dart (96%) rename lib/{pods => core}/translate.freezed.dart (100%) rename lib/{pods => core}/translate.g.dart (100%) rename lib/{ => core}/utils/abuse_report_utils.dart (100%) rename lib/{ => core}/utils/activity_utils.dart (98%) rename lib/{ => core}/utils/file_icon_utils.dart (95%) rename lib/{ => core}/utils/format.dart (100%) rename lib/{ => core}/utils/mapping.dart (100%) rename lib/{ => core}/utils/share_utils.dart (85%) rename lib/{ => core}/utils/text.dart (100%) rename lib/{pods => core}/websocket.dart (98%) rename lib/{pods => core}/websocket.freezed.dart (100%) rename lib/{pods => core}/websocket.g.dart (100%) rename lib/{ => core}/widgets/content/attachment_preview.dart (98%) rename lib/{ => core}/widgets/content/audio.dart (97%) rename lib/{ => core}/widgets/content/cloud_file_collection.dart (98%) rename lib/{ => core}/widgets/content/cloud_file_lightbox.dart (91%) rename lib/{ => core}/widgets/content/cloud_file_picker.dart (82%) rename lib/{ => core}/widgets/content/embed/embed_list.dart (94%) rename lib/{ => core}/widgets/content/embed/link.dart (99%) rename lib/{ => core}/widgets/content/exif_info_overlay.dart (98%) rename lib/{ => core}/widgets/content/file_action_button.dart (100%) rename lib/{ => core}/widgets/content/file_info_sheet.dart (98%) rename lib/{ => core}/widgets/content/file_viewer_contents.dart (93%) rename lib/{ => core}/widgets/content/image.dart (99%) rename lib/{ => core}/widgets/content/image_control_overlay.dart (100%) rename lib/{ => core}/widgets/content/markdown.dart (97%) rename lib/{ => core}/widgets/content/markdown_latex.dart (100%) rename lib/{ => core}/widgets/content/network_status_sheet.dart (97%) rename lib/{ => core}/widgets/content/profile_decoration.dart (100%) rename lib/{ => core}/widgets/content/profile_decoration.freezed.dart (100%) rename lib/{ => core}/widgets/content/sensitive.dart (100%) rename lib/{ => core}/widgets/content/sheet.dart (100%) rename lib/{ => core}/widgets/content/video.dart (100%) rename lib/{ => core}/widgets/content/video.native.dart (95%) rename lib/{ => core}/widgets/content/video.web.dart (100%) rename lib/{ => core}/widgets/payment/README.md (100%) rename lib/{ => core}/widgets/payment/payment_overlay.dart (98%) rename lib/{ => core}/widgets/share/share_sheet.dart (98%) rename lib/{ => core}/widgets/shared/upload_menu.dart (100%) rename lib/{screens => creators}/creators/hub.dart (97%) rename lib/{screens => creators}/creators/hub.g.dart (100%) rename lib/{screens => creators}/creators/poll/poll_list.dart (93%) rename lib/{screens => creators}/creators/poll/poll_list.g.dart (100%) rename lib/{screens => creators}/creators/posts/post_manage_list.dart (84%) rename lib/{screens => creators}/creators/publishers_form.dart (95%) rename lib/{screens => creators}/creators/publishers_form.g.dart (100%) rename lib/{screens => creators}/creators/sites/site_detail.dart (61%) rename lib/{screens => creators}/creators/sites/site_detail.g.dart (100%) rename lib/{screens => creators}/creators/sites/site_edit.dart (96%) rename lib/{screens => creators}/creators/sites/site_list.dart (94%) rename lib/{screens => creators}/creators/sites/widgets/site_config_form.dart (99%) rename lib/{screens => creators}/creators/stickers/pack_detail.dart (96%) rename lib/{screens => creators}/creators/stickers/pack_detail.freezed.dart (100%) rename lib/{screens => creators}/creators/stickers/pack_detail.g.dart (100%) rename lib/{screens => creators}/creators/stickers/stickers.dart (94%) rename lib/{screens => creators}/creators/stickers/stickers.g.dart (100%) rename lib/{screens => creators}/creators/webfeed/webfeed_edit.dart (97%) rename lib/{screens => creators}/creators/webfeed/webfeed_list.dart (76%) rename lib/{models => creators}/publication_site.dart (100%) rename lib/{models => creators}/publication_site.freezed.dart (100%) rename lib/{models => creators}/publication_site.g.dart (100%) rename lib/{database => data}/database.native.dart (90%) rename lib/{database => data}/database.web.dart (93%) rename lib/{database => data}/draft.dart (100%) rename lib/{database => data}/drift_db.dart (98%) rename lib/{database => data}/drift_db.g.dart (100%) rename lib/{database => data}/drift_db.steps.dart (100%) rename lib/{database => data}/message.dart (98%) rename lib/{screens => developers}/developers/app_detail.dart (93%) rename lib/{screens => developers}/developers/app_secrets.dart (96%) rename lib/{screens => developers}/developers/app_secrets.g.dart (100%) rename lib/{screens => developers}/developers/apps.dart (94%) rename lib/{screens => developers}/developers/apps.g.dart (100%) rename lib/{screens => developers}/developers/bot_detail.dart (93%) rename lib/{screens => developers}/developers/bot_keys.dart (96%) rename lib/{screens => developers}/developers/bot_keys.g.dart (100%) rename lib/{screens => developers}/developers/bots.dart (94%) rename lib/{screens => developers}/developers/bots.g.dart (100%) rename lib/{screens => developers}/developers/edit_app.dart (97%) rename lib/{screens => developers}/developers/edit_app.g.dart (100%) rename lib/{screens => developers}/developers/edit_bot.dart (97%) rename lib/{screens => developers}/developers/edit_bot.g.dart (100%) rename lib/{screens => developers}/developers/edit_project.dart (51%) rename lib/{screens => developers}/developers/edit_project.g.dart (100%) rename lib/{screens => developers}/developers/hub.dart (97%) rename lib/{screens => developers}/developers/hub.g.dart (100%) rename lib/{screens => developers}/developers/new_app.dart (88%) rename lib/{screens => developers}/developers/new_bot.dart (88%) rename lib/{screens => developers}/developers/new_project.dart (82%) rename lib/{screens => developers}/developers/project_detail_view.dart (95%) rename lib/{models => developers/developers_models}/bot.dart (92%) rename lib/{models => developers/developers_models}/bot.freezed.dart (100%) rename lib/{models => developers/developers_models}/bot.g.dart (100%) rename lib/{models => developers/developers_models}/bot_key.dart (100%) rename lib/{models => developers/developers_models}/bot_key.freezed.dart (100%) rename lib/{models => developers/developers_models}/bot_key.g.dart (100%) rename lib/{models => developers/developers_models}/custom_app.dart (94%) rename lib/{models => developers/developers_models}/custom_app.freezed.dart (100%) rename lib/{models => developers/developers_models}/custom_app.g.dart (100%) rename lib/{models => developers/developers_models}/custom_app_secret.dart (100%) rename lib/{models => developers/developers_models}/custom_app_secret.freezed.dart (100%) rename lib/{models => developers/developers_models}/custom_app_secret.g.dart (100%) rename lib/{models => developers/developers_models}/dev_project.dart (100%) rename lib/{models => developers/developers_models}/developer.dart (91%) rename lib/{models => developers/developers_models}/developer.freezed.dart (100%) rename lib/{models => developers/developers_models}/developer.g.dart (100%) rename lib/{screens => discovery}/discovery/article_detail.dart (78%) rename lib/{pods/article_detail.dart => discovery/discovery/article_pod.dart} (84%) rename lib/{screens => discovery}/discovery/articles.dart (94%) rename lib/{screens => discovery}/discovery/articles.freezed.dart (100%) rename lib/{screens => discovery}/discovery/articles.g.dart (100%) rename lib/{screens => discovery}/discovery/feeds/feed_detail.dart (94%) rename lib/{screens => discovery}/discovery/feeds/feed_detail.g.dart (100%) rename lib/{screens => discovery}/discovery/feeds/feed_marketplace.dart (94%) rename lib/{screens => discovery}/discovery/realms.dart (94%) rename lib/{models => discovery/discovery_models}/auto_completion.dart (100%) rename lib/{models => discovery/discovery_models}/auto_completion.freezed.dart (100%) rename lib/{models => discovery/discovery_models}/auto_completion.g.dart (100%) rename lib/{models => discovery/discovery_models}/autocomplete_response.dart (100%) rename lib/{models => discovery/discovery_models}/autocomplete_response.freezed.dart (100%) rename lib/{models => discovery/discovery_models}/autocomplete_response.g.dart (100%) rename lib/{models => discovery/discovery_models}/site_file.dart (100%) rename lib/{models => discovery/discovery_models}/site_file.freezed.dart (100%) rename lib/{models => discovery/discovery_models}/site_file.g.dart (100%) rename lib/{models => discovery/discovery_models}/webfeed.dart (96%) rename lib/{models => discovery/discovery_models}/webfeed.freezed.dart (100%) rename lib/{models => discovery/discovery_models}/webfeed.g.dart (100%) rename lib/{services/autocomplete_service.dart => discovery/discovery_service.dart} (89%) rename lib/{screens => discovery}/explore.dart (93%) rename lib/{screens => discovery}/search.dart (95%) rename lib/{widgets => discovery}/web_article_card.dart (98%) rename lib/{pods => discovery}/webfeed.dart (96%) rename lib/{pods => drive}/drive/file_list.dart (95%) rename lib/{pods => drive}/drive/file_list.g.dart (100%) rename lib/{pods => drive}/drive/file_pool.dart (81%) rename lib/{pods => drive}/drive/upload_tasks.dart (79%) rename lib/{models => drive/drive_models}/drive_task.dart (96%) rename lib/{models => drive/drive_models}/drive_task.freezed.dart (100%) rename lib/{models => drive/drive_models}/drive_task.g.dart (100%) rename lib/{models => drive/drive_models}/file.dart (98%) rename lib/{models => drive/drive_models}/file.freezed.dart (100%) rename lib/{models => drive/drive_models}/file.g.dart (100%) rename lib/{models => drive/drive_models}/file_list_item.dart (87%) rename lib/{models => drive/drive_models}/file_list_item.freezed.dart (100%) rename lib/{models => drive/drive_models}/file_pool.dart (100%) rename lib/{models => drive/drive_models}/file_pool.freezed.dart (100%) rename lib/{models => drive/drive_models}/file_pool.g.dart (100%) rename lib/{models => drive/drive_models}/folder.dart (100%) rename lib/{models => drive/drive_models}/folder.freezed.dart (100%) rename lib/{models => drive/drive_models}/folder.g.dart (100%) rename lib/{services/file_uploader.dart => drive/drive_service.dart} (78%) rename lib/{widgets/content => drive/drive_widgets}/cloud_files.dart (98%) rename lib/{widgets => drive/drive_widgets}/file_list_view.dart (98%) rename lib/{screens => drive}/files/file_detail.dart (93%) rename lib/{screens => drive}/files/file_list.dart (95%) rename lib/{services => fitness}/fitness_data.dart (100%) rename lib/{screens/fitness_activity.dart => fitness/fitness_screen.dart} (98%) rename lib/{services => fitness}/fitness_service.dart (99%) rename lib/{screens => lotteries}/lottery.dart (98%) rename lib/{screens => lotteries}/lottery.g.dart (96%) rename lib/{screens => notifications}/notification.dart (93%) rename lib/{screens => notifications}/notification.g.dart (100%) rename lib/{widgets => notifications}/notification_item.dart (98%) rename lib/{widgets => notifications}/notification_overlay.dart (96%) rename lib/{widgets => notifications}/notification_tile.dart (97%) rename lib/{pods/paging.dart => pagination/pagination.dart} (100%) delete mode 100644 lib/pods/database.dart rename lib/{screens => polls}/poll/poll_editor.dart (70%) rename lib/{widgets => polls/polls_widgets}/poll/poll_feedback.dart (92%) rename lib/{widgets => polls/polls_widgets}/poll/poll_stats_widget.dart (99%) rename lib/{widgets => polls/polls_widgets}/poll/poll_submit.dart (91%) rename lib/{widgets => posts}/activity_heatmap.dart (99%) rename lib/{screens => }/posts/compose.dart (84%) rename lib/{screens => }/posts/compose.freezed.dart (100%) rename lib/{screens => }/posts/compose.g.dart (100%) rename lib/{screens => }/posts/compose_article.dart (94%) rename lib/{services => posts}/compose_storage_db.dart (94%) rename lib/{services => posts}/compose_storage_db.g.dart (100%) rename lib/{pods => posts}/post/post_categories.dart (91%) rename lib/{pods => posts}/post/post_list.dart (97%) rename lib/{pods => posts}/post/post_list.freezed.dart (100%) create mode 100644 lib/posts/posts/compose.dart create mode 100644 lib/posts/posts/compose.freezed.dart create mode 100644 lib/posts/posts/compose.g.dart create mode 100644 lib/posts/posts/compose_article.dart rename lib/{screens => posts}/posts/post_categories_list.dart (95%) rename lib/{screens => posts}/posts/post_category_detail.dart (95%) rename lib/{screens => posts}/posts/post_category_detail.g.dart (100%) rename lib/{screens => posts}/posts/post_detail.dart (93%) rename lib/{screens => posts}/posts/post_detail.g.dart (100%) rename lib/{screens => posts}/posts/publisher_profile.dart (95%) rename lib/{screens => posts}/posts/publisher_profile.g.dart (100%) rename lib/{models => posts/posts_models}/embed.dart (100%) rename lib/{models => posts/posts_models}/embed.freezed.dart (100%) rename lib/{models => posts/posts_models}/embed.g.dart (100%) rename lib/{models => posts/posts_models}/heatmap.dart (100%) rename lib/{models => posts/posts_models}/heatmap.freezed.dart (100%) rename lib/{models => posts/posts_models}/heatmap.g.dart (100%) rename lib/{models => posts/posts_models}/poll.dart (96%) rename lib/{models => posts/posts_models}/poll.freezed.dart (100%) rename lib/{models => posts/posts_models}/poll.g.dart (100%) rename lib/{models => posts/posts_models}/post.dart (93%) rename lib/{models => posts/posts_models}/post.freezed.dart (100%) rename lib/{models => posts/posts_models}/post.g.dart (100%) rename lib/{models => posts/posts_models}/post_category.dart (89%) rename lib/{models => posts/posts_models}/post_category.freezed.dart (100%) rename lib/{models => posts/posts_models}/post_category.g.dart (100%) rename lib/{models => posts/posts_models}/post_tag.dart (89%) rename lib/{models => posts/posts_models}/post_tag.freezed.dart (100%) rename lib/{models => posts/posts_models}/post_tag.g.dart (100%) rename lib/{models => posts/posts_models}/publisher.dart (91%) rename lib/{models => posts/posts_models}/publisher.freezed.dart (100%) rename lib/{models => posts/posts_models}/publisher.g.dart (100%) rename lib/{pods/timeline.dart => posts/posts_pod.dart} (91%) rename lib/{widgets/post => posts/posts_widgets}/compose_sheet.dart (94%) rename lib/{widgets => posts/posts_widgets}/post/article_sidebar_panel.dart (100%) rename lib/{widgets => posts/posts_widgets}/post/compose_attachments.dart (79%) rename lib/{widgets => posts/posts_widgets}/post/compose_card.dart (93%) rename lib/{widgets => posts/posts_widgets}/post/compose_dialog.dart (78%) rename lib/{widgets => posts/posts_widgets}/post/compose_embed_sheet.dart (86%) rename lib/{widgets => posts/posts_widgets}/post/compose_form_fields.dart (95%) rename lib/{widgets => posts/posts_widgets}/post/compose_fund.dart (98%) rename lib/{widgets => posts/posts_widgets}/post/compose_info_banner.dart (98%) rename lib/{widgets => posts/posts_widgets}/post/compose_link_attachments.dart (95%) rename lib/{widgets => posts/posts_widgets}/post/compose_poll.dart (62%) rename lib/{widgets => posts/posts_widgets}/post/compose_recorder.dart (91%) rename lib/{widgets => posts/posts_widgets}/post/compose_settings_sheet.dart (97%) rename lib/{widgets => posts/posts_widgets}/post/compose_shared.dart (96%) create mode 100644 lib/posts/posts_widgets/post/compose_sheet.dart rename lib/{widgets => posts/posts_widgets}/post/compose_state_utils.dart (90%) rename lib/{widgets => posts/posts_widgets}/post/compose_toolbar.dart (95%) rename lib/{widgets => posts/posts_widgets}/post/draft_manager.dart (94%) rename lib/{widgets => posts/posts_widgets}/post/embed_view_renderer.dart (99%) rename lib/{widgets => posts/posts_widgets}/post/filters/post_filter.dart (99%) rename lib/{widgets => posts/posts_widgets}/post/filters/post_subscription_filter.dart (97%) rename lib/{widgets => posts/posts_widgets}/post/filters/post_subscription_filter.g.dart (100%) rename lib/{widgets => posts/posts_widgets}/post/post_award_history_sheet.dart (93%) rename lib/{widgets => posts/posts_widgets}/post/post_award_sheet.dart (96%) rename lib/{widgets => posts/posts_widgets}/post/post_featured.dart (97%) rename lib/{widgets => posts/posts_widgets}/post/post_featured.g.dart (100%) rename lib/{widgets => posts/posts_widgets}/post/post_item.dart (95%) rename lib/{widgets => posts/posts_widgets}/post/post_item_creator.dart (96%) rename lib/{widgets => posts/posts_widgets}/post/post_item_screenshot.dart (96%) rename lib/{widgets => posts/posts_widgets}/post/post_item_skeleton.dart (100%) rename lib/{widgets => posts/posts_widgets}/post/post_list.dart (87%) rename lib/{widgets => posts/posts_widgets}/post/post_pin_sheet.dart (81%) rename lib/{widgets => posts/posts_widgets}/post/post_quick_reply.dart (90%) rename lib/{widgets => posts/posts_widgets}/post/post_reaction_sheet.dart (97%) rename lib/{widgets => posts/posts_widgets}/post/post_reaction_sheet.freezed.dart (100%) rename lib/{widgets => posts/posts_widgets}/post/post_replies.dart (87%) rename lib/{widgets => posts/posts_widgets}/post/post_replies_sheet.dart (56%) rename lib/{widgets => posts/posts_widgets}/post/post_shared.dart (97%) rename lib/{widgets => posts/posts_widgets}/post/post_shared.g.dart (100%) rename lib/{widgets => posts/posts_widgets}/post/post_shuffle.dart (97%) rename lib/{widgets => posts/posts_widgets}/post/publishers_modal.dart (96%) rename lib/{widgets => posts/posts_widgets}/publisher/publisher_card.dart (96%) create mode 100644 lib/posts/publisher_profile.dart create mode 100644 lib/posts/publisher_profile.g.dart rename lib/{screens => realms}/realm/realm_detail.dart (96%) rename lib/{screens => realms}/realm/realm_form.dart (95%) rename lib/{screens => realms}/realm/realms.dart (92%) rename lib/{screens => realms}/realm/realms.g.dart (100%) rename lib/{models => realms/realms_models}/realm.dart (91%) rename lib/{models => realms/realms_models}/realm.freezed.dart (100%) rename lib/{models => realms/realms_models}/realm.g.dart (100%) rename lib/{widgets => realms/realms_widgets}/realm/realm_card.dart (96%) rename lib/{widgets => realms/realms_widgets}/realm/realm_list.dart (87%) rename lib/{widgets => realms/realms_widgets}/realm/realm_list_tile.dart (95%) rename lib/{widgets => realms/realms_widgets}/realm/realm_selection_dropdown.dart (94%) rename lib/{widgets => realms/realms_widgets}/realm/realm_tile.dart (82%) rename lib/{screens => reports}/reports/report_detail.dart (92%) rename lib/{screens => reports}/reports/report_list.dart (87%) rename lib/{widgets => reports/reports_widgets}/safety/abuse_report_helper.dart (72%) rename lib/{widgets => reports/reports_widgets}/safety/abuse_report_sheet.dart (81%) delete mode 100644 lib/screens/activitypub/search.dart delete mode 100644 lib/screens/chat/public_room_preview.dart delete mode 100644 lib/services/file_download.dart rename lib/{screens => settings}/about.dart (98%) rename lib/{screens => settings}/dashboard/dash.dart (95%) rename lib/{screens => settings}/dashboard/dash_customize.dart (98%) rename lib/{screens => settings}/settings.dart (98%) rename lib/{screens/tabs.dart => settings/tabs_screen.dart} (95%) rename lib/{screens => settings}/tray_manager.dart (100%) rename lib/{ => shared}/widgets/alert.dart (98%) rename lib/{ => shared}/widgets/app_notification.dart (96%) rename lib/{ => shared}/widgets/app_scaffold.dart (96%) rename lib/{ => shared}/widgets/app_wrapper.dart (92%) rename lib/{ => shared}/widgets/attachment_uploader.dart (97%) rename lib/{ => shared}/widgets/empty_state.dart (100%) rename lib/{ => shared}/widgets/extended_refresh_indicator.dart (100%) rename lib/{widgets/sites => shared/widgets}/info_row.dart (100%) rename lib/{ => shared}/widgets/loading_indicator.dart (100%) rename lib/{widgets/paging => shared/widgets}/pagination_list.dart (98%) rename lib/{ => shared}/widgets/response.dart (98%) rename lib/{widgets/common => shared/widgets}/responsive_sidebar.dart (90%) rename lib/{ => shared}/widgets/task_overlay.dart (99%) rename lib/{pods => sites}/site_files.dart (97%) rename lib/{pods => sites}/site_files.g.dart (100%) rename lib/{pods => sites}/site_pages.dart (96%) rename lib/{pods => sites}/site_pages.g.dart (100%) rename lib/{widgets/sites => sites/sites_widgets}/file_item.dart (96%) rename lib/{widgets/sites => sites/sites_widgets}/file_management_action_section.dart (95%) rename lib/{widgets/sites => sites/sites_widgets}/file_management_section.dart (97%) rename lib/{widgets/sites => sites/sites_widgets}/file_upload_dialog.dart (78%) rename lib/{widgets/sites => sites/sites_widgets}/page_form.dart (99%) rename lib/{widgets/sites => sites/sites_widgets}/page_item.dart (93%) rename lib/{widgets/sites => sites/sites_widgets}/pages_section.dart (79%) rename lib/{widgets/sites => sites/sites_widgets}/site_action_menu.dart (89%) rename lib/{widgets/sites => sites/sites_widgets}/site_detail_content.dart (63%) rename lib/{widgets/sites => sites/sites_widgets}/site_info_card.dart (88%) rename lib/{screens => stickers}/stickers/pack_detail.dart (96%) rename lib/{screens => stickers}/stickers/pack_detail.g.dart (100%) rename lib/{screens => stickers}/stickers/sticker_marketplace.dart (97%) rename lib/{screens => stickers}/stickers/sticker_marketplace.freezed.dart (100%) rename lib/{models => stickers/stickers_models}/sticker.dart (90%) rename lib/{models => stickers/stickers_models}/sticker.freezed.dart (100%) rename lib/{models => stickers/stickers_models}/sticker.g.dart (100%) rename lib/{widgets => stickers/stickers_widgets}/stickers/sticker_picker.dart (98%) rename lib/{widgets => stickers/stickers_widgets}/stickers/sticker_picker.g.dart (100%) rename lib/{models => thought}/thought.dart (99%) rename lib/{models => thought}/thought.freezed.dart (100%) rename lib/{models => thought}/thought.g.dart (100%) rename lib/{screens => thought}/thought/think.dart (51%) rename lib/{screens => thought}/thought/think.g.dart (100%) rename lib/{screens => thought}/thought/think_sheet.dart (92%) rename lib/{widgets => thought/thought_widgets}/thought/function_calls_section.dart (100%) rename lib/{widgets => thought/thought_widgets}/thought/proposals_section.dart (100%) rename lib/{widgets => thought/thought_widgets}/thought/reasoning_section.dart (100%) rename lib/{widgets => thought/thought_widgets}/thought/thought_content.dart (91%) rename lib/{widgets => thought/thought_widgets}/thought/thought_header.dart (100%) rename lib/{widgets => thought/thought_widgets}/thought/thought_proposal.dart (100%) rename lib/{widgets => thought/thought_widgets}/thought/thought_sequence_list.dart (85%) rename lib/{widgets => thought/thought_widgets}/thought/thought_shared.dart (97%) rename lib/{widgets => thought/thought_widgets}/thought/token_info.dart (96%) rename lib/{screens => wallet}/wallet.dart (98%) rename lib/{screens => wallet}/wallet.g.dart (94%) rename lib/{models => wallet/wallet_models}/wallet.dart (99%) rename lib/{models => wallet/wallet_models}/wallet.freezed.dart (100%) rename lib/{models => wallet/wallet_models}/wallet.g.dart (100%) rename lib/{widgets => wallet/wallet_widgets}/wallet/fund_envelope.dart (99%) rename lib/{widgets => wallet/wallet_widgets}/wallet/fund_envelope.g.dart (96%) delete mode 100644 lib/widgets/account/activity_presence.dart diff --git a/lib/services/abuse_report_service.dart b/lib/accounts/abuse_report_service.dart similarity index 86% rename from lib/services/abuse_report_service.dart rename to lib/accounts/abuse_report_service.dart index ef69f933..542468fc 100644 --- a/lib/services/abuse_report_service.dart +++ b/lib/accounts/abuse_report_service.dart @@ -1,6 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/abuse_report.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/accounts/accounts_models/abuse_report.dart'; +import 'package:island/core/network.dart'; final abuseReportServiceProvider = Provider((ref) { return AbuseReportService(ref); diff --git a/lib/screens/account/credits.dart b/lib/accounts/account/credits.dart similarity index 95% rename from lib/screens/account/credits.dart rename to lib/accounts/account/credits.dart index 20bcf901..51b4bb56 100644 --- a/lib/screens/account/credits.dart +++ b/lib/accounts/account/credits.dart @@ -4,11 +4,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/network.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/account/credits.g.dart b/lib/accounts/account/credits.g.dart similarity index 100% rename from lib/screens/account/credits.g.dart rename to lib/accounts/account/credits.g.dart diff --git a/lib/screens/account/leveling.dart b/lib/accounts/account/leveling.dart similarity index 93% rename from lib/screens/account/leveling.dart rename to lib/accounts/account/leveling.dart index 087aa845..d1c61e7e 100644 --- a/lib/screens/account/leveling.dart +++ b/lib/accounts/account/leveling.dart @@ -3,17 +3,17 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/screens/account/credits.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/account/leveling_progress.dart'; -import 'package:island/widgets/account/stellar_program_tab.dart'; -import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/accounts/accounts_widgets/account/leveling_progress.dart'; +import 'package:island/accounts/accounts_widgets/account/stellar_program_tab.dart'; +import 'package:island/core/network.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/accounts/account/credits.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:styled_widget/styled_widget.dart'; final levelingHistoryNotifierProvider = diff --git a/lib/screens/account/me/account_settings.dart b/lib/accounts/account/me/account_settings.dart similarity index 96% rename from lib/screens/account/me/account_settings.dart rename to lib/accounts/account/me/account_settings.dart index 6e2f3d36..4c81b2a9 100644 --- a/lib/screens/account/me/account_settings.dart +++ b/lib/accounts/account/me/account_settings.dart @@ -6,19 +6,19 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/auth.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/screens/account/me/settings_auth_factors.dart'; -import 'package:island/screens/account/me/settings_connections.dart'; -import 'package:island/screens/account/me/settings_contacts.dart'; -import 'package:island/screens/auth/captcha.dart'; -import 'package:island/screens/auth/login.dart'; -import 'package:island/widgets/account/account_devices.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/accounts/accounts_widgets/account/account_devices.dart'; +import 'package:island/auth/auth_models/auth.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/accounts/account/me/settings_auth_factors.dart'; +import 'package:island/accounts/account/me/settings_connections.dart'; +import 'package:island/accounts/account/me/settings_contacts.dart'; +import 'package:island/auth/captcha.dart'; +import 'package:island/auth/login.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/account/me/account_settings.g.dart b/lib/accounts/account/me/account_settings.g.dart similarity index 100% rename from lib/screens/account/me/account_settings.g.dart rename to lib/accounts/account/me/account_settings.g.dart diff --git a/lib/screens/account/me/profile_update.dart b/lib/accounts/account/me/profile_update.dart similarity index 98% rename from lib/screens/account/me/profile_update.dart rename to lib/accounts/account/me/profile_update.dart index 1e104923..605ba2f7 100644 --- a/lib/screens/account/me/profile_update.dart +++ b/lib/accounts/account/me/profile_update.dart @@ -7,17 +7,17 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/services/file.dart'; -import 'package:island/services/file_uploader.dart'; -import 'package:island/services/timezone.dart'; -import 'package:island/widgets/account/account_name.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/accounts/accounts_widgets/account/account_name.dart'; +import 'package:island/core/services/image.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/core/services/timezone.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/account/me/settings_auth_factors.dart b/lib/accounts/account/me/settings_auth_factors.dart similarity index 83% rename from lib/screens/account/me/settings_auth_factors.dart rename to lib/accounts/account/me/settings_auth_factors.dart index 86ea4c7a..c72de26e 100644 --- a/lib/screens/account/me/settings_auth_factors.dart +++ b/lib/accounts/account/me/settings_auth_factors.dart @@ -7,11 +7,11 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/auth.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/auth/login.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/auth/auth_models/auth.dart'; +import 'package:island/core/network.dart'; +import 'package:island/auth/login.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -64,38 +64,37 @@ class AuthFactorSheet extends HookConsumerWidget { if ([3].contains(factor.type)) { final confirmed = await showDialog( context: context, - builder: - (context) => AlertDialog( - title: Text('authFactorEnable').tr(), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text('authFactorEnableHint').tr(), - const SizedBox(height: 16), - OtpTextField( - showCursor: false, - numberOfFields: 6, - obscureText: false, - showFieldAsBox: true, - focusedBorderColor: Theme.of(context).colorScheme.primary, - onSubmit: (String verificationCode) { - password = verificationCode; - }, - textStyle: Theme.of(context).textTheme.titleLarge!, - ), - ], + builder: (context) => AlertDialog( + title: Text('authFactorEnable').tr(), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('authFactorEnableHint').tr(), + const SizedBox(height: 16), + OtpTextField( + showCursor: false, + numberOfFields: 6, + obscureText: false, + showFieldAsBox: true, + focusedBorderColor: Theme.of(context).colorScheme.primary, + onSubmit: (String verificationCode) { + password = verificationCode; + }, + textStyle: Theme.of(context).textTheme.titleLarge!, ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(false), - child: Text('cancel').tr(), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(true), - child: Text('confirm').tr(), - ), - ], + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text('cancel').tr(), ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text('confirm').tr(), + ), + ], + ), ); if (confirmed == false || (password?.isEmpty ?? true) || @@ -233,19 +232,18 @@ class AuthFactorNewSheet extends HookConsumerWidget { labelText: 'authFactor'.tr(), border: const OutlineInputBorder(), ), - items: - kFactorTypes.entries.map((entry) { - return DropdownMenuItem( - value: entry.key, - child: Row( - children: [ - Icon(entry.value.$3), - const Gap(8), - Text(entry.value.$1).tr(), - ], - ), - ); - }).toList(), + items: kFactorTypes.entries.map((entry) { + return DropdownMenuItem( + value: entry.key, + child: Row( + children: [ + Icon(entry.value.$3), + const Gap(8), + Text(entry.value.$1).tr(), + ], + ), + ); + }).toList(), onChanged: (value) { if (value != null) { factorType.value = value; @@ -261,8 +259,8 @@ class AuthFactorNewSheet extends HookConsumerWidget { hintText: 'authFactorSecretHint'.tr(), border: const OutlineInputBorder(), ), - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), ) else if ([4].contains(factorType.value)) OtpTextField( diff --git a/lib/screens/account/me/settings_connections.dart b/lib/accounts/account/me/settings_connections.dart similarity index 63% rename from lib/screens/account/me/settings_connections.dart rename to lib/accounts/account/me/settings_connections.dart index e3640fc6..802090b8 100644 --- a/lib/screens/account/me/settings_connections.dart +++ b/lib/accounts/account/me/settings_connections.dart @@ -4,15 +4,15 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/auth.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/account/me/account_settings.dart'; -import 'package:island/utils/text.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/auth/auth_models/auth.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/account/me/account_settings.dart'; +import 'package:island/core/utils/text.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:sign_in_with_apple/sign_in_with_apple.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -233,17 +233,14 @@ class AccountConnectionNewSheet extends HookConsumerWidget { labelText: 'accountConnectionProvider'.tr(), border: const OutlineInputBorder(), ), - items: - providers.map((String provider) { - return DropdownMenuItem( - value: provider, - child: Row( - children: [ - Text(getLocalizedProviderName(provider)).tr(), - ], - ), - ); - }).toList(), + items: providers.map((String provider) { + return DropdownMenuItem( + value: provider, + child: Row( + children: [Text(getLocalizedProviderName(provider)).tr()], + ), + ); + }).toList(), onChanged: (String? newValue) { if (newValue != null) { selectedProvider.value = newValue; @@ -296,104 +293,88 @@ class AccountConnectionsSheet extends HookConsumerWidget { ), ], child: connections.when( - data: - (data) => RefreshIndicator( - onRefresh: - () => Future.sync( - () => ref.invalidate(accountConnectionsProvider), - ), - child: - data.isEmpty - ? Center( - child: Text( - 'accountConnectionsEmpty'.tr(), - textAlign: TextAlign.center, - ).padding(horizontal: 32), - ) - : ListView.builder( - padding: EdgeInsets.zero, - itemCount: data.length, - itemBuilder: (context, index) { - final connection = data[index]; - return Dismissible( - key: Key('connection-${connection.id}'), - direction: DismissDirection.endToStart, - background: Container( - color: Colors.red, - alignment: Alignment.centerRight, - padding: const EdgeInsets.symmetric( - horizontal: 20, - ), - child: const Icon( - Icons.delete, - color: Colors.white, - ), - ), - confirmDismiss: (direction) async { - final confirm = await showConfirmAlert( - 'accountConnectionDeleteHint'.tr(), - 'accountConnectionDelete'.tr(), - isDanger: true, - ); - if (confirm && context.mounted) { - try { - final client = ref.read(apiClientProvider); - await client.delete( - '/pass/accounts/me/connections/${connection.id}', - ); - ref.invalidate(accountConnectionsProvider); - return true; - } catch (err) { - showErrorAlert(err); - return false; - } - } - return false; - }, - child: ListTile( - leading: getProviderIcon( - connection.provider, - color: Theme.of(context).colorScheme.onSurface, - ), - title: - Text( - getLocalizedProviderName( - connection.provider, - ), - ).tr(), - subtitle: - connection.meta['email'] != null - ? Text(connection.meta['email']) - : Text(connection.providedIdentifier), - trailing: Text( - DateFormat.yMd().format( - connection.lastUsedAt.toLocal(), - ), - style: Theme.of(context).textTheme.bodySmall, - ), - onTap: () async { - final result = await showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: - (context) => AccountConnectionSheet( - connection: connection, - ), - ); - if (result == true) { - ref.invalidate(accountConnectionsProvider); - } - }, - ), + data: (data) => RefreshIndicator( + onRefresh: () => + Future.sync(() => ref.invalidate(accountConnectionsProvider)), + child: data.isEmpty + ? Center( + child: Text( + 'accountConnectionsEmpty'.tr(), + textAlign: TextAlign.center, + ).padding(horizontal: 32), + ) + : ListView.builder( + padding: EdgeInsets.zero, + itemCount: data.length, + itemBuilder: (context, index) { + final connection = data[index]; + return Dismissible( + key: Key('connection-${connection.id}'), + direction: DismissDirection.endToStart, + background: Container( + color: Colors.red, + alignment: Alignment.centerRight, + padding: const EdgeInsets.symmetric(horizontal: 20), + child: const Icon(Icons.delete, color: Colors.white), + ), + confirmDismiss: (direction) async { + final confirm = await showConfirmAlert( + 'accountConnectionDeleteHint'.tr(), + 'accountConnectionDelete'.tr(), + isDanger: true, + ); + if (confirm && context.mounted) { + try { + final client = ref.read(apiClientProvider); + await client.delete( + '/pass/accounts/me/connections/${connection.id}', + ); + ref.invalidate(accountConnectionsProvider); + return true; + } catch (err) { + showErrorAlert(err); + return false; + } + } + return false; + }, + child: ListTile( + leading: getProviderIcon( + connection.provider, + color: Theme.of(context).colorScheme.onSurface, + ), + title: Text( + getLocalizedProviderName(connection.provider), + ).tr(), + subtitle: connection.meta['email'] != null + ? Text(connection.meta['email']) + : Text(connection.providedIdentifier), + trailing: Text( + DateFormat.yMd().format( + connection.lastUsedAt.toLocal(), + ), + style: Theme.of(context).textTheme.bodySmall, + ), + onTap: () async { + final result = await showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => + AccountConnectionSheet(connection: connection), ); + if (result == true) { + ref.invalidate(accountConnectionsProvider); + } }, ), - ), - error: - (err, _) => ResponseErrorWidget( - error: err, - onRetry: () => ref.invalidate(accountConnectionsProvider), - ), + ); + }, + ), + ), + error: (err, _) => ResponseErrorWidget( + error: err, + onRetry: () => ref.invalidate(accountConnectionsProvider), + ), loading: () => const ResponseLoadingWidget(), ), ); diff --git a/lib/screens/account/me/settings_contacts.dart b/lib/accounts/account/me/settings_contacts.dart similarity index 94% rename from lib/screens/account/me/settings_contacts.dart rename to lib/accounts/account/me/settings_contacts.dart index f24b02ca..c1dc1108 100644 --- a/lib/screens/account/me/settings_contacts.dart +++ b/lib/accounts/account/me/settings_contacts.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -153,10 +153,9 @@ class ContactMethodSheet extends HookConsumerWidget { child: Badge( label: Text('contactMethodPrivate'.tr()), textColor: Theme.of(context).colorScheme.onSurface, - backgroundColor: - Theme.of( - context, - ).colorScheme.surfaceContainerHighest, + backgroundColor: Theme.of( + context, + ).colorScheme.surfaceContainerHighest, ), ), ], @@ -319,12 +318,11 @@ class ContactMethodNewSheet extends HookConsumerWidget { ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: - Text(switch (contactType.value) { - 0 => 'contactMethodEmailDescription', - 1 => 'contactMethodPhoneDescription', - _ => 'contactMethodAddressDescription', - }).tr(), + child: Text(switch (contactType.value) { + 0 => 'contactMethodEmailDescription', + 1 => 'contactMethodPhoneDescription', + _ => 'contactMethodAddressDescription', + }).tr(), ), Row( mainAxisAlignment: MainAxisAlignment.end, diff --git a/lib/screens/account/profile.dart b/lib/accounts/account/profile.dart similarity index 96% rename from lib/screens/account/profile.dart rename to lib/accounts/account/profile.dart index e7fded52..d4ed7afb 100644 --- a/lib/screens/account/profile.dart +++ b/lib/accounts/account/profile.dart @@ -7,33 +7,33 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/models/developer.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/models/relationship.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/event_calendar.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/services/color.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/utils/text.dart'; -import 'package:island/services/time.dart'; -import 'package:island/services/timezone/native.dart'; -import 'package:island/widgets/account/account_name.dart'; -import 'package:island/widgets/account/activity_presence.dart'; -import 'package:island/widgets/account/badge.dart'; -import 'package:island/widgets/account/fortune_graph.dart'; -import 'package:island/widgets/account/leveling_progress.dart'; -import 'package:island/widgets/account/status.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/markdown.dart'; -import 'package:island/widgets/safety/abuse_report_helper.dart'; +import 'package:island/accounts/accounts_widgets/account/account_name.dart'; +import 'package:island/accounts/accounts_widgets/account/activity_presence.dart'; +import 'package:island/accounts/accounts_widgets/account/badge.dart'; +import 'package:island/accounts/accounts_widgets/account/fortune_graph.dart'; +import 'package:island/accounts/accounts_widgets/account/leveling_progress.dart'; +import 'package:island/accounts/accounts_widgets/account/status.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/developers/developers_models/developer.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/accounts/accounts_models/relationship.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/config.dart'; +import 'package:island/accounts/event_calendar.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/core/services/color.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/core/utils/text.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/core/services/timezone/native.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/markdown.dart'; +import 'package:island/reports/reports_widgets/safety/abuse_report_helper.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:island/services/color_extraction.dart'; +import 'package:island/core/services/color_extraction.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:share_plus/share_plus.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/account/profile.g.dart b/lib/accounts/account/profile.g.dart similarity index 100% rename from lib/screens/account/profile.g.dart rename to lib/accounts/account/profile.g.dart diff --git a/lib/screens/account/relationship.dart b/lib/accounts/account/relationship.dart similarity index 96% rename from lib/screens/account/relationship.dart rename to lib/accounts/account/relationship.dart index a622e540..d0f66b33 100644 --- a/lib/screens/account/relationship.dart +++ b/lib/accounts/account/relationship.dart @@ -6,19 +6,19 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/widgets/account/account_pfc.dart'; -import 'package:island/widgets/account/account_picker.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/accounts/accounts_widgets/account/account_pfc.dart'; +import 'package:island/accounts/accounts_widgets/account/account_picker.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:relative_time/relative_time.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:island/models/relationship.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/accounts/accounts_models/relationship.dart'; +import 'package:island/core/network.dart'; part 'relationship.g.dart'; @@ -112,7 +112,7 @@ class RelationshipListTile extends StatelessWidget { return ListTile( contentPadding: const EdgeInsets.only(left: 16, right: 12), - leading: AccountPfcGestureDetector( + leading: AccountPfcRegion( uname: account.name, child: ProfilePictureWidget(file: account.profile.picture), ), diff --git a/lib/screens/account/relationship.g.dart b/lib/accounts/account/relationship.g.dart similarity index 100% rename from lib/screens/account/relationship.g.dart rename to lib/accounts/account/relationship.g.dart diff --git a/lib/models/abuse_report.dart b/lib/accounts/accounts_models/abuse_report.dart similarity index 100% rename from lib/models/abuse_report.dart rename to lib/accounts/accounts_models/abuse_report.dart diff --git a/lib/models/abuse_report.freezed.dart b/lib/accounts/accounts_models/abuse_report.freezed.dart similarity index 100% rename from lib/models/abuse_report.freezed.dart rename to lib/accounts/accounts_models/abuse_report.freezed.dart diff --git a/lib/models/abuse_report.g.dart b/lib/accounts/accounts_models/abuse_report.g.dart similarity index 100% rename from lib/models/abuse_report.g.dart rename to lib/accounts/accounts_models/abuse_report.g.dart diff --git a/lib/models/abuse_report_type.dart b/lib/accounts/accounts_models/abuse_report_type.dart similarity index 100% rename from lib/models/abuse_report_type.dart rename to lib/accounts/accounts_models/abuse_report_type.dart diff --git a/lib/models/account.dart b/lib/accounts/accounts_models/account.dart similarity index 97% rename from lib/models/account.dart rename to lib/accounts/accounts_models/account.dart index 2a67ea9f..2889514f 100644 --- a/lib/models/account.dart +++ b/lib/accounts/accounts_models/account.dart @@ -1,8 +1,8 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/activity.dart'; -import 'package:island/models/auth.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/wallet.dart'; +import 'package:island/core/models/activity.dart'; +import 'package:island/auth/auth_models/auth.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/wallet/wallet_models/wallet.dart'; part 'account.freezed.dart'; part 'account.g.dart'; diff --git a/lib/models/account.freezed.dart b/lib/accounts/accounts_models/account.freezed.dart similarity index 100% rename from lib/models/account.freezed.dart rename to lib/accounts/accounts_models/account.freezed.dart diff --git a/lib/models/account.g.dart b/lib/accounts/accounts_models/account.g.dart similarity index 100% rename from lib/models/account.g.dart rename to lib/accounts/accounts_models/account.g.dart diff --git a/lib/models/badge.dart b/lib/accounts/accounts_models/badge.dart similarity index 100% rename from lib/models/badge.dart rename to lib/accounts/accounts_models/badge.dart diff --git a/lib/models/fortune.dart b/lib/accounts/accounts_models/fortune.dart similarity index 100% rename from lib/models/fortune.dart rename to lib/accounts/accounts_models/fortune.dart diff --git a/lib/models/fortune.freezed.dart b/lib/accounts/accounts_models/fortune.freezed.dart similarity index 100% rename from lib/models/fortune.freezed.dart rename to lib/accounts/accounts_models/fortune.freezed.dart diff --git a/lib/models/fortune.g.dart b/lib/accounts/accounts_models/fortune.g.dart similarity index 100% rename from lib/models/fortune.g.dart rename to lib/accounts/accounts_models/fortune.g.dart diff --git a/lib/models/relationship.dart b/lib/accounts/accounts_models/relationship.dart similarity index 91% rename from lib/models/relationship.dart rename to lib/accounts/accounts_models/relationship.dart index 8f04bea4..e93d4a89 100644 --- a/lib/models/relationship.dart +++ b/lib/accounts/accounts_models/relationship.dart @@ -1,6 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/account.dart'; +import 'package:island/accounts/accounts_models/account.dart'; part 'relationship.freezed.dart'; part 'relationship.g.dart'; diff --git a/lib/models/relationship.freezed.dart b/lib/accounts/accounts_models/relationship.freezed.dart similarity index 100% rename from lib/models/relationship.freezed.dart rename to lib/accounts/accounts_models/relationship.freezed.dart diff --git a/lib/models/relationship.g.dart b/lib/accounts/accounts_models/relationship.g.dart similarity index 100% rename from lib/models/relationship.g.dart rename to lib/accounts/accounts_models/relationship.g.dart diff --git a/lib/pods/userinfo.dart b/lib/accounts/accounts_pod.dart similarity index 91% rename from lib/pods/userinfo.dart rename to lib/accounts/accounts_pod.dart index 64bf9009..3992ce85 100644 --- a/lib/pods/userinfo.dart +++ b/lib/accounts/accounts_pod.dart @@ -2,14 +2,14 @@ import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:island/widgets/alert.dart'; +import 'package:island/shared/widgets/alert.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; import 'package:island/talker.dart'; -import 'package:island/services/analytics_service.dart'; +import 'package:island/core/services/analytics_service.dart'; class UserInfoNotifier extends AsyncNotifier { @override diff --git a/lib/screens/account.dart b/lib/accounts/accounts_screen.dart similarity index 96% rename from lib/screens/account.dart rename to lib/accounts/accounts_screen.dart index 7ea9bac8..307fe4f0 100644 --- a/lib/screens/account.dart +++ b/lib/accounts/accounts_screen.dart @@ -3,20 +3,20 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/message.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/pods/websocket.dart'; -import 'package:island/screens/notification.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/account/account_name.dart'; -import 'package:island/widgets/account/activity_presence.dart'; -import 'package:island/widgets/account/status.dart'; -import 'package:island/widgets/account/leveling_progress.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/debug_sheet.dart'; +import 'package:island/accounts/accounts_widgets/account/account_name.dart'; +import 'package:island/accounts/accounts_widgets/account/activity_presence.dart'; +import 'package:island/accounts/accounts_widgets/account/leveling_progress.dart'; +import 'package:island/accounts/accounts_widgets/account/status.dart'; +import 'package:island/core/websocket.dart'; +import 'package:island/core/database.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/debug_sheet.dart'; +import 'package:island/notifications/notification.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/account/account_devices.dart b/lib/accounts/accounts_widgets/account/account_devices.dart similarity index 56% rename from lib/widgets/account/account_devices.dart rename to lib/accounts/accounts_widgets/account/account_devices.dart index ea6def1b..9179cb9b 100644 --- a/lib/widgets/account/account_devices.dart +++ b/lib/accounts/accounts_widgets/account/account_devices.dart @@ -4,19 +4,19 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/services/time.dart'; -import 'package:island/services/udid.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/response.dart'; -import 'package:island/widgets/sites/info_row.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/core/services/udid.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/response.dart'; +import 'package:island/shared/widgets/info_row.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; part 'account_devices.g.dart'; @@ -26,11 +26,10 @@ Future> authDevices(Ref ref) async { .watch(apiClientProvider) .get('/pass/accounts/me/devices'); final currentId = await getUdid(); - final data = - resp.data.map((e) { - final ele = SnAuthDeviceWithSession.fromJson(e); - return ele.copyWith(isCurrent: ele.deviceId == currentId); - }).toList(); + final data = resp.data.map((e) { + final ele = SnAuthDeviceWithSession.fromJson(e); + return ele.copyWith(isCurrent: ele.deviceId == currentId); + }).toList(); return data; } @@ -91,25 +90,24 @@ class _DeviceListTile extends StatelessWidget { 6 => Icons.computer, // Linux _ => Icons.device_unknown, // fallback }).padding(top: 4), - trailing: - isWideScreen(context) - ? Row( - mainAxisSize: MainAxisSize.min, - children: [ + trailing: isWideScreen(context) + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.edit), + tooltip: 'authDeviceEditLabel'.tr(), + onPressed: () => updateDeviceLabel(device.deviceId), + ), + if (!device.isCurrent) IconButton( - icon: Icon(Icons.edit), - tooltip: 'authDeviceEditLabel'.tr(), - onPressed: () => updateDeviceLabel(device.deviceId), + icon: Icon(Icons.logout), + tooltip: 'authDeviceLogout'.tr(), + onPressed: () => logoutDevice(device.deviceId), ), - if (!device.isCurrent) - IconButton( - icon: Icon(Icons.logout), - tooltip: 'authDeviceLogout'.tr(), - onPressed: () => logoutDevice(device.deviceId), - ), - ], - ) - : null, + ], + ) + : null, expandedCrossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( @@ -216,29 +214,28 @@ class AccountSessionSheet extends HookConsumerWidget { final controller = TextEditingController(); final label = await showDialog( context: context, - builder: - (context) => AlertDialog( - title: Text('authDeviceLabelTitle'.tr()), - content: TextField( - controller: controller, - decoration: InputDecoration( - isDense: true, - border: const OutlineInputBorder(), - hintText: 'authDeviceLabelHint'.tr(), - ), - autofocus: true, - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text('cancel'.tr()), - ), - TextButton( - onPressed: () => Navigator.pop(context, controller.text), - child: Text('confirm'.tr()), - ), - ], + builder: (context) => AlertDialog( + title: Text('authDeviceLabelTitle'.tr()), + content: TextField( + controller: controller, + decoration: InputDecoration( + isDense: true, + border: const OutlineInputBorder(), + hintText: 'authDeviceLabelHint'.tr(), ), + autofocus: true, + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text('cancel'.tr()), + ), + TextButton( + onPressed: () => Navigator.pop(context, controller.text), + child: Text('confirm'.tr()), + ), + ], + ), ); if (label == null || label.isEmpty || !context.mounted) return; try { @@ -283,90 +280,83 @@ class AccountSessionSheet extends HookConsumerWidget { ), Expanded( child: authDevices.when( - data: - (data) => ExtendedRefreshIndicator( - onRefresh: - () => Future.sync( - () => ref.invalidate(authDevicesProvider), + data: (data) => ExtendedRefreshIndicator( + onRefresh: () => + Future.sync(() => ref.invalidate(authDevicesProvider)), + child: ListView.builder( + padding: EdgeInsets.zero, + itemCount: data.length, + itemBuilder: (context, index) { + final device = data[index]; + if (wideScreen) { + return _DeviceListTile( + device: device, + updateDeviceLabel: updateDeviceLabel, + logoutDevice: logoutDevice, + logoutSession: logoutSession, + ); + } else { + return Dismissible( + key: Key('device-${device.id}'), + direction: device.isCurrent + ? DismissDirection.startToEnd + : DismissDirection.horizontal, + background: Container( + color: Colors.blue, + alignment: Alignment.centerLeft, + padding: EdgeInsets.symmetric(horizontal: 20), + child: Icon(Icons.edit, color: Colors.white), ), - child: ListView.builder( - padding: EdgeInsets.zero, - itemCount: data.length, - itemBuilder: (context, index) { - final device = data[index]; - if (wideScreen) { - return _DeviceListTile( - device: device, - updateDeviceLabel: updateDeviceLabel, - logoutDevice: logoutDevice, - logoutSession: logoutSession, - ); - } else { - return Dismissible( - key: Key('device-${device.id}'), - direction: - device.isCurrent - ? DismissDirection.startToEnd - : DismissDirection.horizontal, - background: Container( - color: Colors.blue, - alignment: Alignment.centerLeft, - padding: EdgeInsets.symmetric(horizontal: 20), - child: Icon(Icons.edit, color: Colors.white), - ), - secondaryBackground: Container( - color: Colors.red, - alignment: Alignment.centerRight, - padding: EdgeInsets.symmetric(horizontal: 20), - child: Icon(Icons.logout, color: Colors.white), - ), - confirmDismiss: (direction) async { - if (direction == DismissDirection.startToEnd) { - updateDeviceLabel(device.deviceId); - return false; - } else { - final confirm = await showConfirmAlert( - 'authDeviceLogoutHint'.tr(), - 'authDeviceLogout'.tr(), - isDanger: true, + secondaryBackground: Container( + color: Colors.red, + alignment: Alignment.centerRight, + padding: EdgeInsets.symmetric(horizontal: 20), + child: Icon(Icons.logout, color: Colors.white), + ), + confirmDismiss: (direction) async { + if (direction == DismissDirection.startToEnd) { + updateDeviceLabel(device.deviceId); + return false; + } else { + final confirm = await showConfirmAlert( + 'authDeviceLogoutHint'.tr(), + 'authDeviceLogout'.tr(), + isDanger: true, + ); + if (confirm && context.mounted) { + try { + showLoadingModal(context); + final apiClient = ref.watch(apiClientProvider); + await apiClient.delete( + '/pass/accounts/me/devices/${device.deviceId}', ); - if (confirm && context.mounted) { - try { - showLoadingModal(context); - final apiClient = ref.watch( - apiClientProvider, - ); - await apiClient.delete( - '/pass/accounts/me/devices/${device.deviceId}', - ); - ref.invalidate(authDevicesProvider); - } catch (err) { - showErrorAlert(err); - } finally { - if (context.mounted) { - hideLoadingModal(context); - } - } + ref.invalidate(authDevicesProvider); + } catch (err) { + showErrorAlert(err); + } finally { + if (context.mounted) { + hideLoadingModal(context); } - return confirm; } - }, - child: _DeviceListTile( - device: device, - updateDeviceLabel: updateDeviceLabel, - logoutDevice: logoutDevice, - logoutSession: logoutSession, - ), - ); - } - }, - ), - ), - error: - (err, _) => ResponseErrorWidget( - error: err, - onRetry: () => ref.invalidate(authDevicesProvider), - ), + } + return confirm; + } + }, + child: _DeviceListTile( + device: device, + updateDeviceLabel: updateDeviceLabel, + logoutDevice: logoutDevice, + logoutSession: logoutSession, + ), + ); + } + }, + ), + ), + error: (err, _) => ResponseErrorWidget( + error: err, + onRetry: () => ref.invalidate(authDevicesProvider), + ), loading: () => ResponseLoadingWidget(), ), ), diff --git a/lib/widgets/account/account_devices.g.dart b/lib/accounts/accounts_widgets/account/account_devices.g.dart similarity index 100% rename from lib/widgets/account/account_devices.g.dart rename to lib/accounts/accounts_widgets/account/account_devices.g.dart diff --git a/lib/widgets/account/account_name.dart b/lib/accounts/accounts_widgets/account/account_name.dart similarity index 98% rename from lib/widgets/account/account_name.dart rename to lib/accounts/accounts_widgets/account/account_name.dart index 51e061ac..d0d87a88 100644 --- a/lib/widgets/account/account_name.dart +++ b/lib/accounts/accounts_widgets/account/account_name.dart @@ -2,10 +2,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/wallet.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/alert.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/wallet/wallet_models/wallet.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/account/account_nameplate.dart b/lib/accounts/accounts_widgets/account/account_nameplate.dart similarity index 97% rename from lib/widgets/account/account_nameplate.dart rename to lib/accounts/accounts_widgets/account/account_nameplate.dart index 0c940be1..66859fb1 100644 --- a/lib/widgets/account/account_nameplate.dart +++ b/lib/accounts/accounts_widgets/account/account_nameplate.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:island/widgets/account/account_name.dart'; +import 'package:island/accounts/account/profile.dart'; +import 'package:island/accounts/accounts_widgets/account/account_name.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/screens/account/profile.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:easy_localization/easy_localization.dart'; diff --git a/lib/widgets/account/account_pfc.dart b/lib/accounts/accounts_widgets/account/account_pfc.dart similarity index 92% rename from lib/widgets/account/account_pfc.dart rename to lib/accounts/accounts_widgets/account/account_pfc.dart index 5b1dce25..777516b2 100644 --- a/lib/widgets/account/account_pfc.dart +++ b/lib/accounts/accounts_widgets/account/account_pfc.dart @@ -7,16 +7,15 @@ import 'package:go_router/go_router.dart'; import 'package:flutter_popup_card/flutter_popup_card.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/screens/account/profile.dart'; -import 'package:island/services/time.dart'; -import 'package:island/services/timezone/native.dart'; -import 'package:island/widgets/account/account_name.dart'; -import 'package:island/widgets/account/activity_presence.dart'; -import 'package:island/widgets/account/badge.dart'; - -import 'package:island/widgets/account/status.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/accounts/account/profile.dart'; +import 'package:island/accounts/accounts_widgets/account/account_name.dart'; +import 'package:island/accounts/accounts_widgets/account/activity_presence.dart'; +import 'package:island/accounts/accounts_widgets/account/badge.dart'; +import 'package:island/accounts/accounts_widgets/account/status.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/core/services/timezone/native.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -225,14 +224,10 @@ class AccountProfileCard extends HookConsumerWidget { } } -class AccountPfcGestureDetector extends StatelessWidget { +class AccountPfcRegion extends StatelessWidget { final String? uname; final Widget child; - const AccountPfcGestureDetector({ - super.key, - required this.uname, - required this.child, - }); + const AccountPfcRegion({super.key, required this.uname, required this.child}); @override Widget build(BuildContext context) { diff --git a/lib/widgets/account/account_picker.dart b/lib/accounts/accounts_widgets/account/account_picker.dart similarity index 95% rename from lib/widgets/account/account_picker.dart rename to lib/accounts/accounts_widgets/account/account_picker.dart index 7e9cc8ab..afe8baa6 100644 --- a/lib/widgets/account/account_picker.dart +++ b/lib/accounts/accounts_widgets/account/account_picker.dart @@ -4,9 +4,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/network.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'account_picker.g.dart'; diff --git a/lib/widgets/account/account_picker.g.dart b/lib/accounts/accounts_widgets/account/account_picker.g.dart similarity index 100% rename from lib/widgets/account/account_picker.g.dart rename to lib/accounts/accounts_widgets/account/account_picker.g.dart diff --git a/lib/accounts/accounts_widgets/account/activity_presence.dart b/lib/accounts/accounts_widgets/account/activity_presence.dart new file mode 100644 index 00000000..c2de982c --- /dev/null +++ b/lib/accounts/accounts_widgets/account/activity_presence.dart @@ -0,0 +1,618 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:dio/dio.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/core/models/activity.dart'; +import 'package:island/activity/activity_rpc.dart'; +import 'package:island/core/widgets/content/image.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:styled_widget/styled_widget.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +part 'activity_presence.g.dart'; + +@riverpod +Future?> discordAssets( + Ref ref, + SnPresenceActivity activity, +) async { + final hasDiscordSmall = + activity.smallImage != null && + activity.smallImage!.startsWith('discord:'); + final hasDiscordLarge = + activity.largeImage != null && + activity.largeImage!.startsWith('discord:'); + + if (hasDiscordSmall || hasDiscordLarge) { + final dio = Dio(); + final response = await dio.get( + 'https://discordapp.com/api/oauth2/applications/${activity.manualId}/assets', + ); + final data = response.data as List; + return { + for (final item in data) item['name'] as String: item['id'] as String, + }; + } + + return null; +} + +@riverpod +Future discordAssetsUrl( + Ref ref, + SnPresenceActivity activity, + String key, +) async { + final assets = await ref.watch(discordAssetsProvider(activity).future); + if (assets != null && assets.containsKey(key)) { + final assetId = assets[key]!; + return 'https://cdn.discordapp.com/app-assets/${activity.manualId}/$assetId.png'; + } + return null; +} + +const kPresenceActivityTypes = [ + 'unknown', + 'presenceTypeGaming', + 'presenceTypeMusic', + 'presenceTypeWorkout', +]; + +const kPresenceActivityIcons = [ + Symbols.question_mark_rounded, + Symbols.play_arrow_rounded, + Symbols.music_note_rounded, + Symbols.running_with_errors, +]; + +class ActivityPresenceWidget extends StatefulWidget { + final String uname; + final bool isCompact; + final EdgeInsets compactPadding; + + const ActivityPresenceWidget({ + super.key, + required this.uname, + this.isCompact = false, + this.compactPadding = EdgeInsets.zero, + }); + + @override + State createState() => _ActivityPresenceWidgetState(); +} + +class _ActivityPresenceWidgetState extends State + with TickerProviderStateMixin { + late AnimationController _progressController; + late Animation _progressAnimation; + double _startProgress = 0.0; + double _endProgress = 0.0; + + @override + void initState() { + super.initState(); + _progressController = AnimationController( + vsync: this, + duration: const Duration(seconds: 1), + ); + _progressAnimation = Tween( + begin: 0.0, + end: 0.0, + ).animate(_progressController); + } + + @override + void dispose() { + _progressController.dispose(); + super.dispose(); + } + + List _buildImages(WidgetRef ref, SnPresenceActivity activity) { + final List images = []; + + if (activity.largeImage != null) { + if (activity.largeImage!.startsWith('discord:')) { + final key = activity.largeImage!.substring('discord:'.length); + final urlAsync = ref.watch(discordAssetsUrlProvider(activity, key)); + images.add( + urlAsync.when( + data: (url) => url != null + ? ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CachedNetworkImage( + imageUrl: url, + width: 64, + height: 64, + ), + ) + : const SizedBox.shrink(), + loading: () => const SizedBox( + width: 64, + height: 64, + child: CircularProgressIndicator(strokeWidth: 2), + ), + error: (error, stack) => const SizedBox.shrink(), + ), + ); + } else { + images.add( + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: UniversalImage( + uri: activity.largeImage!, + width: 64, + height: 64, + ), + ), + ); + } + } + + if (activity.smallImage != null) { + if (activity.smallImage!.startsWith('discord:')) { + final key = activity.smallImage!.substring('discord:'.length); + final urlAsync = ref.watch(discordAssetsUrlProvider(activity, key)); + images.add( + urlAsync.when( + data: (url) => url != null + ? ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CachedNetworkImage( + imageUrl: url, + width: 32, + height: 32, + ), + ) + : const SizedBox.shrink(), + loading: () => const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2), + ), + error: (error, stack) => const SizedBox.shrink(), + ), + ); + } else { + images.add( + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: UniversalImage( + uri: activity.smallImage!, + width: 32, + height: 32, + ), + ), + ); + } + } + + return images; + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (BuildContext context, WidgetRef ref, Widget? child) { + final activitiesAsync = ref.watch( + presenceActivitiesProvider(widget.uname), + ); + + if (widget.isCompact) { + return activitiesAsync.when( + data: (activities) { + if (activities.isEmpty) return const SizedBox.shrink(); + final activity = activities.first; + return Padding( + padding: widget.compactPadding, + child: Row( + spacing: 8, + children: [ + if (activity.largeImage != null) + activity.largeImage!.startsWith('discord:') + ? ref + .watch( + discordAssetsUrlProvider( + activity, + activity.largeImage!.substring( + 'discord:'.length, + ), + ), + ) + .when( + data: (url) => url != null + ? ClipRRect( + borderRadius: BorderRadius.circular( + 4, + ), + child: CachedNetworkImage( + imageUrl: url, + width: 32, + height: 32, + ), + ) + : const SizedBox.shrink(), + loading: () => const SizedBox( + width: 32, + height: 32, + child: CircularProgressIndicator( + strokeWidth: 1, + ), + ), + error: (error, stack) => + const SizedBox.shrink(), + ) + : ClipRRect( + borderRadius: BorderRadius.circular(4), + child: UniversalImage( + uri: activity.largeImage!, + width: 32, + height: 32, + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + (activity.title?.isEmpty ?? true) + ? 'unknown'.tr() + : activity.title!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ).fontSize(13), + Row( + children: [ + Text( + kPresenceActivityTypes[activity.type], + ).tr().fontSize(11), + Icon( + kPresenceActivityIcons[activity.type], + size: 15, + fill: 1, + ), + ], + ), + ], + ), + ), + StreamBuilder( + stream: Stream.periodic(const Duration(seconds: 1)), + builder: (context, snapshot) { + final now = DateTime.now(); + + if (activity.manualId == 'spotify' && + activity.meta != null) { + final meta = activity.meta as Map; + final progressMs = meta['progress_ms'] as int? ?? 0; + final durationMs = + meta['track_duration_ms'] as int? ?? 1; + final elapsed = now + .difference(activity.createdAt) + .inMilliseconds; + final currentProgressMs = + (progressMs + elapsed) % durationMs; + final progressValue = currentProgressMs / durationMs; + if (progressValue != _endProgress) { + _startProgress = _endProgress; + _endProgress = progressValue; + _progressAnimation = Tween( + begin: _startProgress, + end: _endProgress, + ).animate(_progressController); + _progressController.forward(from: 0.0); + } + return AnimatedBuilder( + animation: _progressAnimation, + builder: (context, child) { + final animatedValue = _progressAnimation.value; + final animatedProgressMs = + (animatedValue * durationMs).toInt(); + final currentMin = animatedProgressMs ~/ 60000; + final currentSec = + (animatedProgressMs % 60000) ~/ 1000; + return Column( + crossAxisAlignment: CrossAxisAlignment.end, + spacing: 2, + children: [ + Text( + '${currentMin.toString().padLeft(2, '0')}:${currentSec.toString().padLeft(2, '0')}', + style: TextStyle( + fontSize: 10, + color: Colors.green, + ), + ), + SizedBox( + width: 120, + child: LinearProgressIndicator( + value: animatedValue, + backgroundColor: Colors.grey.shade300, + stopIndicatorColor: Colors.green, + trackGap: 0, + valueColor: AlwaysStoppedAnimation( + Colors.green, + ), + ), + ).padding(top: 2), + ], + ); + }, + ); + } else { + final duration = now.difference(activity.createdAt); + final hours = duration.inHours.toString().padLeft( + 2, + '0', + ); + final minutes = (duration.inMinutes % 60) + .toString() + .padLeft(2, '0'); + final seconds = (duration.inSeconds % 60) + .toString() + .padLeft(2, '0'); + return Text( + '$hours:$minutes:$seconds', + ).textColor(Colors.green).fontSize(12); + } + }, + ), + ], + ), + ); + }, + loading: () => const SizedBox.shrink(), + error: (error, stack) => const SizedBox.shrink(), + ); + } + + return activitiesAsync.when( + data: (activities) => Card( + margin: EdgeInsets.zero, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8, + children: [ + Text( + 'activities', + ).tr().bold().padding(horizontal: 16, vertical: 4), + if (activities.isEmpty) + Row( + spacing: 4, + children: [ + const Icon(Symbols.inbox, size: 16), + Text('dataEmpty').tr().fontSize(13), + ], + ).opacity(0.75).padding(horizontal: 16, bottom: 8), + ...activities.map((activity) { + final images = _buildImages(ref, activity); + + return Stack( + children: [ + Card( + elevation: 0, + shape: RoundedRectangleBorder( + side: BorderSide( + color: Colors.grey.shade300, + width: 1, + ), + borderRadius: BorderRadius.circular(8), + ), + margin: EdgeInsets.zero, + child: ListTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (images.isNotEmpty) + Row( + crossAxisAlignment: CrossAxisAlignment.end, + spacing: 8, + children: images, + ).padding(vertical: 4), + Row( + spacing: 2, + children: [ + Flexible( + child: Text( + (activity.title?.isEmpty ?? true) + ? 'unknown'.tr() + : activity.title!, + ), + ), + if (activity.titleUrl != null && + activity.titleUrl!.isNotEmpty) + IconButton( + visualDensity: const VisualDensity( + vertical: -4, + ), + onPressed: () { + launchUrlString(activity.titleUrl!); + }, + icon: const Icon(Symbols.launch_rounded), + iconSize: 16, + padding: EdgeInsets.all(4), + constraints: const BoxConstraints( + maxWidth: 28, + maxHeight: 28, + ), + ), + ], + ), + ], + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 4, + children: [ + Text( + kPresenceActivityTypes[activity.type], + ).tr(), + Icon( + kPresenceActivityIcons[activity.type], + size: 16, + fill: 1, + ), + ], + ), + if (activity.manualId == 'spotify' && + activity.meta != null) + StreamBuilder( + stream: Stream.periodic( + const Duration(seconds: 1), + ), + builder: (context, snapshot) { + final now = DateTime.now(); + final meta = + activity.meta as Map; + final progressMs = + meta['progress_ms'] as int? ?? 0; + final durationMs = + meta['track_duration_ms'] as int? ?? 1; + final elapsed = now + .difference(activity.createdAt) + .inMilliseconds; + final currentProgressMs = + (progressMs + elapsed) % durationMs; + final progressValue = + currentProgressMs / durationMs; + if (progressValue != _endProgress) { + _startProgress = _endProgress; + _endProgress = progressValue; + _progressAnimation = Tween( + begin: _startProgress, + end: _endProgress, + ).animate(_progressController); + _progressController.forward(from: 0.0); + } + return AnimatedBuilder( + animation: _progressAnimation, + builder: (context, child) { + final animatedValue = + _progressAnimation.value; + final animatedProgressMs = + (animatedValue * durationMs) + .toInt(); + final currentMin = + animatedProgressMs ~/ 60000; + final currentSec = + (animatedProgressMs % 60000) ~/ + 1000; + final totalMin = durationMs ~/ 60000; + final totalSec = + (durationMs % 60000) ~/ 1000; + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + spacing: 4, + children: [ + LinearProgressIndicator( + value: animatedValue, + backgroundColor: + Colors.grey.shade300, + trackGap: 0, + stopIndicatorColor: Colors.green, + valueColor: + AlwaysStoppedAnimation( + Colors.green, + ), + ).padding(top: 3), + Text( + '${currentMin.toString().padLeft(2, '0')}:${currentSec.toString().padLeft(2, '0')} / ${totalMin.toString().padLeft(2, '0')}:${totalSec.toString().padLeft(2, '0')}', + style: TextStyle( + fontSize: 12, + color: Colors.green, + ), + ), + ], + ); + }, + ); + }, + ) + else + StreamBuilder( + stream: Stream.periodic( + const Duration(seconds: 1), + ), + builder: (context, snapshot) { + final now = DateTime.now(); + + final duration = now.difference( + activity.createdAt, + ); + final hours = duration.inHours + .toString() + .padLeft(2, '0'); + final minutes = (duration.inMinutes % 60) + .toString() + .padLeft(2, '0'); + final seconds = (duration.inSeconds % 60) + .toString() + .padLeft(2, '0'); + return Text( + '$hours:$minutes:$seconds', + ).textColor(Colors.green); + }, + ), + if (activity.subtitle?.isNotEmpty ?? false) + Row( + spacing: 2, + children: [ + Flexible(child: Text(activity.subtitle!)), + if (activity.titleUrl != null && + activity.titleUrl!.isNotEmpty) + IconButton( + visualDensity: const VisualDensity( + vertical: -4, + ), + onPressed: () { + launchUrlString(activity.titleUrl!); + }, + icon: const Icon( + Symbols.launch_rounded, + ), + iconSize: 16, + padding: EdgeInsets.all(4), + constraints: const BoxConstraints( + maxWidth: 28, + maxHeight: 28, + ), + ), + ], + ), + if (activity.caption?.isNotEmpty ?? false) + Text(activity.caption!), + ], + ), + ), + ).padding(horizontal: 8), + if (activity.manualId == 'spotify') + Positioned( + top: 16, + right: 24, + child: Tooltip( + message: 'Listening on Spotify', + child: Image.asset( + 'assets/images/oidc/spotify.png', + width: 24, + height: 24, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), + ], + ); + }), + ], + ).padding(horizontal: 8, top: 8, bottom: 16), + ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => + Center(child: Text('Error loading activities: $error')), + ); + }, + ); + } +} diff --git a/lib/widgets/account/activity_presence.g.dart b/lib/accounts/accounts_widgets/account/activity_presence.g.dart similarity index 100% rename from lib/widgets/account/activity_presence.g.dart rename to lib/accounts/accounts_widgets/account/activity_presence.g.dart diff --git a/lib/widgets/account/badge.dart b/lib/accounts/accounts_widgets/account/badge.dart similarity index 90% rename from lib/widgets/account/badge.dart rename to lib/accounts/accounts_widgets/account/badge.dart index e34d0dd2..eeedcd96 100644 --- a/lib/widgets/account/badge.dart +++ b/lib/accounts/accounts_widgets/account/badge.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/badge.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/accounts/accounts_models/badge.dart'; class BadgeList extends StatelessWidget { final List badges; diff --git a/lib/widgets/account/event_calendar.dart b/lib/accounts/accounts_widgets/account/event_calendar.dart similarity index 83% rename from lib/widgets/account/event_calendar.dart rename to lib/accounts/accounts_widgets/account/event_calendar.dart index 14db08d7..3cc18c25 100644 --- a/lib/widgets/account/event_calendar.dart +++ b/lib/accounts/accounts_widgets/account/event_calendar.dart @@ -2,11 +2,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/activity.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/accounts/accounts_widgets/account/event_details_widget.dart'; +import 'package:island/core/models/activity.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:island/widgets/account/event_details_widget.dart'; import 'package:table_calendar/table_calendar.dart'; /// A reusable widget for displaying an event calendar with event details @@ -89,28 +89,28 @@ class EventCalendarWidget extends HookConsumerWidget { return Center(child: Text(text)); }, markerBuilder: (context, day, events) { - final checkInResult = - events.whereType().firstOrNull; + final checkInResult = events + .whereType() + .firstOrNull; final statuses = events.whereType().toList(); - final textColor = - isSameDay(selectedDay.value, day) - ? Colors.white - : isSameDay(DateTime.now(), day) - ? Colors.white - : Theme.of(context).colorScheme.onSurface; + final textColor = isSameDay(selectedDay.value, day) + ? Colors.white + : isSameDay(DateTime.now(), day) + ? Colors.white + : Theme.of(context).colorScheme.onSurface; final shadow = isSameDay(selectedDay.value, day) || - isSameDay(DateTime.now(), day) - ? [ - Shadow( - color: Colors.black.withOpacity(0.5), - offset: const Offset(0, 1), - blurRadius: 4, - ), - ] - : null; + isSameDay(DateTime.now(), day) + ? [ + Shadow( + color: Colors.black.withOpacity(0.5), + offset: const Offset(0, 1), + blurRadius: 4, + ), + ] + : null; if (checkInResult != null) { return Positioned( @@ -152,10 +152,9 @@ class EventCalendarWidget extends HookConsumerWidget { duration: const Duration(milliseconds: 300), child: Builder( builder: (context) { - final event = - events.value - ?.where((e) => isSameDay(e.date, selectedDay.value)) - .firstOrNull; + final event = events.value + ?.where((e) => isSameDay(e.date, selectedDay.value)) + .firstOrNull; return EventDetailsWidget( selectedDay: selectedDay.value, event: event, diff --git a/lib/widgets/account/event_calendar_content.dart b/lib/accounts/accounts_widgets/account/event_calendar_content.dart similarity index 59% rename from lib/widgets/account/event_calendar_content.dart rename to lib/accounts/accounts_widgets/account/event_calendar_content.dart index 24830157..d1349aa0 100644 --- a/lib/widgets/account/event_calendar_content.dart +++ b/lib/accounts/accounts_widgets/account/event_calendar_content.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/event_calendar.dart'; -import 'package:island/screens/account/profile.dart'; -import 'package:island/widgets/account/account_nameplate.dart'; -import 'package:island/widgets/account/event_calendar.dart'; -import 'package:island/widgets/account/fortune_graph.dart'; +import 'package:island/accounts/account/profile.dart'; +import 'package:island/accounts/accounts_widgets/account/account_nameplate.dart'; +import 'package:island/accounts/accounts_widgets/account/event_calendar.dart'; +import 'package:island/accounts/accounts_widgets/account/fortune_graph.dart'; +import 'package:island/accounts/event_calendar.dart'; import 'package:styled_widget/styled_widget.dart'; /// A reusable content widget for event calendar that can be used in screens or sheets @@ -85,65 +85,64 @@ class EventCalendarContent extends HookConsumerWidget { } else { // Screen layout - with responsive design return SingleChildScrollView( - child: - MediaQuery.of(context).size.width > 480 - ? ConstrainedBox( - constraints: BoxConstraints(maxWidth: 480), - child: Column( - children: [ - Card( - margin: EdgeInsets.only(left: 16, right: 16, top: 16), - child: Column( - children: [ - // Use the reusable EventCalendarWidget - EventCalendarWidget( - events: events, - initialDate: now, - showEventDetails: true, - onMonthChanged: onMonthChanged, - onDaySelected: onDaySelected, - ), - ], - ), - ), - - // Add the fortune graph widget - FortuneGraphWidget( - events: events, - constrainWidth: true, - onPointSelected: onDaySelected, - ), - - // Show user profile if viewing someone else's calendar - if (name != 'me' && user.value != null) - AccountNameplate(name: name), - ], - ), - ).center() - : Column( + child: MediaQuery.of(context).size.width > 480 + ? ConstrainedBox( + constraints: BoxConstraints(maxWidth: 480), + child: Column( children: [ - // Use the reusable EventCalendarWidget - EventCalendarWidget( - events: events, - initialDate: now, - showEventDetails: true, - onMonthChanged: onMonthChanged, - onDaySelected: onDaySelected, + Card( + margin: EdgeInsets.only(left: 16, right: 16, top: 16), + child: Column( + children: [ + // Use the reusable EventCalendarWidget + EventCalendarWidget( + events: events, + initialDate: now, + showEventDetails: true, + onMonthChanged: onMonthChanged, + onDaySelected: onDaySelected, + ), + ], + ), ), // Add the fortune graph widget - const Divider(height: 1), FortuneGraphWidget( events: events, + constrainWidth: true, onPointSelected: onDaySelected, - ).padding(horizontal: 8, vertical: 4), + ), // Show user profile if viewing someone else's calendar if (name != 'me' && user.value != null) AccountNameplate(name: name), - Gap(MediaQuery.of(context).padding.bottom + 16), ], ), + ).center() + : Column( + children: [ + // Use the reusable EventCalendarWidget + EventCalendarWidget( + events: events, + initialDate: now, + showEventDetails: true, + onMonthChanged: onMonthChanged, + onDaySelected: onDaySelected, + ), + + // Add the fortune graph widget + const Divider(height: 1), + FortuneGraphWidget( + events: events, + onPointSelected: onDaySelected, + ).padding(horizontal: 8, vertical: 4), + + // Show user profile if viewing someone else's calendar + if (name != 'me' && user.value != null) + AccountNameplate(name: name), + Gap(MediaQuery.of(context).padding.bottom + 16), + ], + ), ); } } diff --git a/lib/widgets/account/event_details_widget.dart b/lib/accounts/accounts_widgets/account/event_details_widget.dart similarity index 96% rename from lib/widgets/account/event_details_widget.dart rename to lib/accounts/accounts_widgets/account/event_details_widget.dart index 845eb0e4..d054a115 100644 --- a/lib/widgets/account/event_details_widget.dart +++ b/lib/accounts/accounts_widgets/account/event_details_widget.dart @@ -1,9 +1,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:island/models/activity.dart'; -import 'package:island/services/time.dart'; -import 'package:island/utils/activity_utils.dart'; +import 'package:island/core/models/activity.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/core/utils/activity_utils.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/account/fortune_graph.dart b/lib/accounts/accounts_widgets/account/fortune_graph.dart similarity index 84% rename from lib/widgets/account/fortune_graph.dart rename to lib/accounts/accounts_widgets/account/fortune_graph.dart index 6a0eff3e..ab6f8caf 100644 --- a/lib/widgets/account/fortune_graph.dart +++ b/lib/accounts/accounts_widgets/account/fortune_graph.dart @@ -2,9 +2,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/activity.dart'; -import 'package:island/widgets/account/event_calendar_content.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/accounts/accounts_widgets/account/event_calendar_content.dart'; +import 'package:island/core/models/activity.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:styled_widget/styled_widget.dart'; /// A widget that displays a graph of fortune levels over time @@ -73,14 +73,13 @@ class FortuneGraphWidget extends HookConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => SheetScaffold( - titleText: 'eventCalendar'.tr(), - child: EventCalendarContent( - name: eventCalandarUser!, - isSheet: true, - ), - ), + builder: (context) => SheetScaffold( + titleText: 'eventCalendar'.tr(), + child: EventCalendarContent( + name: eventCalandarUser!, + isSheet: true, + ), + ), ); }, ), @@ -95,15 +94,14 @@ class FortuneGraphWidget extends HookConsumerWidget { } // Create spots for the line chart - final spots = - data - .map( - (e) => FlSpot( - e.date.millisecondsSinceEpoch.toDouble(), - e.checkInResult!.level.toDouble(), - ), - ) - .toList(); + final spots = data + .map( + (e) => FlSpot( + e.date.millisecondsSinceEpoch.toDouble(), + e.checkInResult!.level.toDouble(), + ), + ) + .toList(); // Get min and max dates for the x-axis final minDate = data.first.date; @@ -194,8 +192,9 @@ class FortuneGraphWidget extends HookConsumerWidget { TextSpan( text: 'checkInResultLevel$level'.tr(), style: TextStyle( - color: - Theme.of(context).colorScheme.onSurface, + color: Theme.of( + context, + ).colorScheme.onSurface, fontWeight: FontWeight.normal, ), ), @@ -204,21 +203,19 @@ class FortuneGraphWidget extends HookConsumerWidget { }).toList(); }, ), - touchCallback: ( - FlTouchEvent event, - LineTouchResponse? response, - ) { - if (event is FlTapUpEvent && - response != null && - response.lineBarSpots != null && - response.lineBarSpots!.isNotEmpty) { - final spot = response.lineBarSpots!.first; - final date = DateTime.fromMillisecondsSinceEpoch( - spot.x.toInt(), - ); - onPointSelected?.call(date); - } - }, + touchCallback: + (FlTouchEvent event, LineTouchResponse? response) { + if (event is FlTapUpEvent && + response != null && + response.lineBarSpots != null && + response.lineBarSpots!.isNotEmpty) { + final spot = response.lineBarSpots!.first; + final date = DateTime.fromMillisecondsSinceEpoch( + spot.x.toInt(), + ); + onPointSelected?.call(date); + } + }, ), lineBarsData: [ LineChartBarData( @@ -234,8 +231,9 @@ class FortuneGraphWidget extends HookConsumerWidget { radius: 4, color: Theme.of(context).colorScheme.primary, strokeWidth: 2, - strokeColor: - Theme.of(context).colorScheme.surface, + strokeColor: Theme.of( + context, + ).colorScheme.surface, ); }, ), diff --git a/lib/widgets/account/friends_overview.dart b/lib/accounts/accounts_widgets/account/friends_overview.dart similarity index 97% rename from lib/widgets/account/friends_overview.dart rename to lib/accounts/accounts_widgets/account/friends_overview.dart index 1a0d379b..ad58ea09 100644 --- a/lib/widgets/account/friends_overview.dart +++ b/lib/accounts/accounts_widgets/account/friends_overview.dart @@ -4,10 +4,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:cached_network_image/cached_network_image.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/widgets/account/account_pfc.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/accounts/accounts_widgets/account/account_pfc.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/config.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -101,7 +101,7 @@ class FriendsOverviewWidget extends HookConsumerWidget { itemCount: onlineFriends.length, itemBuilder: (context, index) { final friend = onlineFriends[index]; - return AccountPfcGestureDetector( + return AccountPfcRegion( uname: friend.account.name, child: _FriendTile(friend: friend), ); diff --git a/lib/widgets/account/friends_overview.g.dart b/lib/accounts/accounts_widgets/account/friends_overview.g.dart similarity index 100% rename from lib/widgets/account/friends_overview.g.dart rename to lib/accounts/accounts_widgets/account/friends_overview.g.dart diff --git a/lib/widgets/account/leveling_progress.dart b/lib/accounts/accounts_widgets/account/leveling_progress.dart similarity index 100% rename from lib/widgets/account/leveling_progress.dart rename to lib/accounts/accounts_widgets/account/leveling_progress.dart diff --git a/lib/widgets/account/restore_purchase_sheet.dart b/lib/accounts/accounts_widgets/account/restore_purchase_sheet.dart similarity index 95% rename from lib/widgets/account/restore_purchase_sheet.dart rename to lib/accounts/accounts_widgets/account/restore_purchase_sheet.dart index e6538817..dc236a85 100644 --- a/lib/widgets/account/restore_purchase_sheet.dart +++ b/lib/accounts/accounts_widgets/account/restore_purchase_sheet.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/account/me/settings_connections.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/account/me/settings_connections.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/account/status.dart b/lib/accounts/accounts_widgets/account/status.dart similarity index 60% rename from lib/widgets/account/status.dart rename to lib/accounts/accounts_widgets/account/status.dart index 00948b0e..22ce1a31 100644 --- a/lib/widgets/account/status.dart +++ b/lib/accounts/accounts_widgets/account/status.dart @@ -2,13 +2,13 @@ import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/screens/account/profile.dart'; -import 'package:island/services/time.dart'; -import 'package:island/utils/activity_utils.dart'; -import 'package:island/widgets/account/status_creation.dart'; +import 'package:island/accounts/account/profile.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/accounts/accounts_widgets/account/status_creation.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/core/utils/activity_utils.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -78,67 +78,61 @@ class AccountStatusCreationWidget extends HookConsumerWidget { return InkWell( borderRadius: BorderRadius.circular(8), child: userStatus.when( - data: - (status) => - (status?.isCustomized ?? false) - ? Padding( - padding: const EdgeInsets.only(left: 4), - child: AccountStatusWidget( - uname: uname, - padding: renderPadding, - ), - ) - : Padding( - padding: renderPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon(Symbols.keyboard_arrow_up), - SizedBox(width: 4), - Text('Create Status').tr(), - ], - ), - SizedBox(height: 4), - Text( - 'Tap to set your current activity and let others know what you\'re up to', - style: TextStyle(fontSize: 12), - ).tr().opacity(0.75), - ], - ), - ).opacity(0.85), - error: - (error, _) => Padding( - padding: - padding ?? EdgeInsets.symmetric(horizontal: 26, vertical: 12), - child: Row( - spacing: 4, - children: [Icon(Symbols.close), Text('Error: $error')], - ), - ).opacity(0.85), - loading: - () => Padding( - padding: - padding ?? EdgeInsets.symmetric(horizontal: 26, vertical: 12), - child: Row( - spacing: 4, - children: [Icon(Symbols.more_vert), Text('loading').tr()], - ), - ).opacity(0.85), + data: (status) => (status?.isCustomized ?? false) + ? Padding( + padding: const EdgeInsets.only(left: 4), + child: AccountStatusWidget( + uname: uname, + padding: renderPadding, + ), + ) + : Padding( + padding: renderPadding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Symbols.keyboard_arrow_up), + SizedBox(width: 4), + Text('Create Status').tr(), + ], + ), + SizedBox(height: 4), + Text( + 'Tap to set your current activity and let others know what you\'re up to', + style: TextStyle(fontSize: 12), + ).tr().opacity(0.75), + ], + ), + ).opacity(0.85), + error: (error, _) => Padding( + padding: + padding ?? EdgeInsets.symmetric(horizontal: 26, vertical: 12), + child: Row( + spacing: 4, + children: [Icon(Symbols.close), Text('Error: $error')], + ), + ).opacity(0.85), + loading: () => Padding( + padding: + padding ?? EdgeInsets.symmetric(horizontal: 26, vertical: 12), + child: Row( + spacing: 4, + children: [Icon(Symbols.more_vert), Text('loading').tr()], + ), + ).opacity(0.85), ), onTap: () { showModalBottomSheet( context: context, isScrollControlled: true, useRootNavigator: true, - builder: - (context) => AccountStatusCreationSheet( - initialStatus: - (userStatus.value?.isCustomized ?? false) - ? userStatus.value - : null, - ), + builder: (context) => AccountStatusCreationSheet( + initialStatus: (userStatus.value?.isCustomized ?? false) + ? userStatus.value + : null, + ), ); }, ); @@ -156,11 +150,11 @@ class AccountStatusWidget extends HookConsumerWidget { final localStatus = ref.watch(currentAccountStatusProvider); final status = (uname == 'me' || - (userInfo.value != null && - uname == userInfo.value!.name && - localStatus != null)) - ? AsyncValue.data(localStatus) - : ref.watch(accountStatusProvider(uname)); + (userInfo.value != null && + uname == userInfo.value!.name && + localStatus != null)) + ? AsyncValue.data(localStatus) + : ref.watch(accountStatusProvider(uname)); final account = ref.watch(accountProvider(uname)); return Padding( @@ -187,17 +181,16 @@ class AccountStatusWidget extends HookConsumerWidget { onLongPress: () { showDialog( context: context, - builder: - (context) => AlertDialog( - title: Text('Activity Details'), - content: buildActivityDetails(status.value), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text('Close'), - ), - ], + builder: (context) => AlertDialog( + title: Text('Activity Details'), + content: buildActivityDetails(status.value), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('Close'), ), + ], + ), ); }, child: Tooltip( @@ -213,12 +206,11 @@ class AccountStatusWidget extends HookConsumerWidget { ) else Flexible( - child: - Text( - (status.value?.label ?? 'offline').toLowerCase(), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ).tr(), + child: Text( + (status.value?.label ?? 'offline').toLowerCase(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ).tr(), ), if (getActivitySubtitle(status.value?.meta) != null) Flexible( diff --git a/lib/widgets/account/status.g.dart b/lib/accounts/accounts_widgets/account/status.g.dart similarity index 100% rename from lib/widgets/account/status.g.dart rename to lib/accounts/accounts_widgets/account/status.g.dart diff --git a/lib/widgets/account/status_creation.dart b/lib/accounts/accounts_widgets/account/status_creation.dart similarity index 91% rename from lib/widgets/account/status_creation.dart rename to lib/accounts/accounts_widgets/account/status_creation.dart index 0888c02e..8838390f 100644 --- a/lib/widgets/account/status_creation.dart +++ b/lib/accounts/accounts_widgets/account/status_creation.dart @@ -4,12 +4,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/widgets/account/status.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/accounts/accounts_widgets/account/status.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; class AccountStatusCreationSheet extends HookConsumerWidget { @@ -74,16 +74,16 @@ class AccountStatusCreationSheet extends HookConsumerWidget { return SheetScaffold( heightFactor: 0.6, - titleText: - initialStatus == null ? 'statusCreate'.tr() : 'statusUpdate'.tr(), + titleText: initialStatus == null + ? 'statusCreate'.tr() + : 'statusUpdate'.tr(), actions: [ TextButton.icon( - onPressed: - submitting.value - ? null - : () { - submitStatus(); - }, + onPressed: submitting.value + ? null + : () { + submitStatus(); + }, icon: const Icon(Symbols.upload), label: Text(initialStatus == null ? 'create' : 'update').tr(), style: ButtonStyle( @@ -116,8 +116,8 @@ class AccountStatusCreationSheet extends HookConsumerWidget { borderRadius: BorderRadius.all(Radius.circular(12)), ), ), - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), ), const SizedBox(height: 24), Text( diff --git a/lib/widgets/account/stellar_program_tab.dart b/lib/accounts/accounts_widgets/account/stellar_program_tab.dart similarity index 98% rename from lib/widgets/account/stellar_program_tab.dart rename to lib/accounts/accounts_widgets/account/stellar_program_tab.dart index d9d5fedc..2aa7aa05 100644 --- a/lib/widgets/account/stellar_program_tab.dart +++ b/lib/accounts/accounts_widgets/account/stellar_program_tab.dart @@ -7,18 +7,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/wallet.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/account/account_pfc.dart'; -import 'package:island/widgets/account/account_picker.dart'; -import 'package:island/widgets/account/restore_purchase_sheet.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/payment/payment_overlay.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/accounts/accounts_widgets/account/account_pfc.dart'; +import 'package:island/accounts/accounts_widgets/account/account_picker.dart'; +import 'package:island/accounts/accounts_widgets/account/restore_purchase_sheet.dart'; +import 'package:island/wallet/wallet_models/wallet.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/core/widgets/payment/payment_overlay.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -1047,7 +1047,7 @@ class StellarProgramTab extends HookConsumerWidget { ), ), if (gift.status == 2 && gift.redeemer != null) - AccountPfcGestureDetector( + AccountPfcRegion( uname: gift.redeemer!.name, child: ProfilePictureWidget( file: gift.redeemer!.profile.picture, diff --git a/lib/widgets/account/stellar_program_tab.g.dart b/lib/accounts/accounts_widgets/account/stellar_program_tab.g.dart similarity index 96% rename from lib/widgets/account/stellar_program_tab.g.dart rename to lib/accounts/accounts_widgets/account/stellar_program_tab.g.dart index ab973e95..cf889d50 100644 --- a/lib/widgets/account/stellar_program_tab.g.dart +++ b/lib/accounts/accounts_widgets/account/stellar_program_tab.g.dart @@ -50,7 +50,7 @@ final class AccountStellarSubscriptionProvider } String _$accountStellarSubscriptionHash() => - r'7cdfc7ca29aac240fc8704f4493498d87f307400'; + r'fd0aa9b7110e5d0ba68d8a57bd0e4dc191586e3b'; @ProviderFor(accountSentGifts) final accountSentGiftsProvider = AccountSentGiftsFamily._(); @@ -109,7 +109,7 @@ final class AccountSentGiftsProvider } } -String _$accountSentGiftsHash() => r'460af8d22e16dc402848cb94e9b8a8a26d023c41'; +String _$accountSentGiftsHash() => r'9fa99729b9efa1a74695645ee1418677b5e63027'; final class AccountSentGiftsFamily extends $Family with @@ -198,7 +198,7 @@ final class AccountReceivedGiftsProvider } String _$accountReceivedGiftsHash() => - r'1208c27cca49e154af073071a197b37a2703f56d'; + r'b9e9ad5e8de8916f881ceeca7f2032f344c5c58b'; final class AccountReceivedGiftsFamily extends $Family with @@ -280,7 +280,7 @@ final class AccountGiftProvider } } -String _$accountGiftHash() => r'70ca553e0b84cba9dfbee428f9bf44207138713a'; +String _$accountGiftHash() => r'78890be44865accadeabdc26a96447bb3e841a5d'; final class AccountGiftFamily extends $Family with $FunctionalFamilyOverride, String> { diff --git a/lib/widgets/activitypub/activitypub.dart b/lib/accounts/accounts_widgets/activitypub/activitypub.dart similarity index 100% rename from lib/widgets/activitypub/activitypub.dart rename to lib/accounts/accounts_widgets/activitypub/activitypub.dart diff --git a/lib/widgets/activitypub/actor_list_item.dart b/lib/accounts/accounts_widgets/activitypub/actor_list_item.dart similarity index 98% rename from lib/widgets/activitypub/actor_list_item.dart rename to lib/accounts/accounts_widgets/activitypub/actor_list_item.dart index 8d1f7112..e5627a8b 100644 --- a/lib/widgets/activitypub/actor_list_item.dart +++ b/lib/accounts/accounts_widgets/activitypub/actor_list_item.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:island/models/activitypub.dart'; +import 'package:island/core/models/activitypub.dart'; import 'package:material_symbols_icons/symbols.dart'; class ApActorListItem extends StatelessWidget { diff --git a/lib/widgets/activitypub/actor_profile.dart b/lib/accounts/accounts_widgets/activitypub/actor_profile.dart similarity index 97% rename from lib/widgets/activitypub/actor_profile.dart rename to lib/accounts/accounts_widgets/activitypub/actor_profile.dart index 4166ca06..17f2d576 100644 --- a/lib/widgets/activitypub/actor_profile.dart +++ b/lib/accounts/accounts_widgets/activitypub/actor_profile.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:island/models/activitypub.dart'; +import 'package:island/core/models/activitypub.dart'; import 'package:material_symbols_icons/symbols.dart'; class ActorPictureWidget extends StatelessWidget { diff --git a/lib/widgets/activitypub/user_list_item.dart b/lib/accounts/accounts_widgets/activitypub/user_list_item.dart similarity index 98% rename from lib/widgets/activitypub/user_list_item.dart rename to lib/accounts/accounts_widgets/activitypub/user_list_item.dart index f7e8d6c6..f4b81b3f 100644 --- a/lib/widgets/activitypub/user_list_item.dart +++ b/lib/accounts/accounts_widgets/activitypub/user_list_item.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:island/models/activitypub.dart'; +import 'package:island/core/models/activitypub.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:relative_time/relative_time.dart'; diff --git a/lib/widgets/check_in.dart b/lib/accounts/check_in.dart similarity index 95% rename from lib/widgets/check_in.dart rename to lib/accounts/check_in.dart index 60d202e4..0aa9b570 100644 --- a/lib/widgets/check_in.dart +++ b/lib/accounts/check_in.dart @@ -7,15 +7,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/activity.dart'; -import 'package:island/models/fortune.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/screens/auth/captcha.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/account/event_calendar_content.dart'; +import 'package:island/accounts/accounts_widgets/account/event_calendar_content.dart'; +import 'package:island/core/models/activity.dart'; +import 'package:island/accounts/accounts_models/fortune.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/auth/captcha.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/check_in.g.dart b/lib/accounts/check_in.g.dart similarity index 100% rename from lib/widgets/check_in.g.dart rename to lib/accounts/check_in.g.dart diff --git a/lib/pods/event_calendar.dart b/lib/accounts/event_calendar.dart similarity index 93% rename from lib/pods/event_calendar.dart rename to lib/accounts/event_calendar.dart index 1006ad5f..78f4dad0 100644 --- a/lib/pods/event_calendar.dart +++ b/lib/accounts/event_calendar.dart @@ -1,5 +1,5 @@ -import 'package:island/models/activity.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/core/models/activity.dart'; +import 'package:island/core/network.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'event_calendar.g.dart'; diff --git a/lib/pods/event_calendar.g.dart b/lib/accounts/event_calendar.g.dart similarity index 100% rename from lib/pods/event_calendar.g.dart rename to lib/accounts/event_calendar.g.dart diff --git a/lib/widgets/usage_overview.dart b/lib/accounts/usage_overview.dart similarity index 99% rename from lib/widgets/usage_overview.dart rename to lib/accounts/usage_overview.dart index 8485aa0f..1a5b452d 100644 --- a/lib/widgets/usage_overview.dart +++ b/lib/accounts/usage_overview.dart @@ -1,7 +1,7 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:island/utils/format.dart'; +import 'package:island/core/utils/format.dart'; import 'package:styled_widget/styled_widget.dart'; class UsageOverviewWidget extends StatelessWidget { diff --git a/lib/pods/activity/activity_rpc.dart b/lib/activity/activity_rpc.dart similarity index 99% rename from lib/pods/activity/activity_rpc.dart rename to lib/activity/activity_rpc.dart index 638de21b..ec674bbf 100644 --- a/lib/pods/activity/activity_rpc.dart +++ b/lib/activity/activity_rpc.dart @@ -4,8 +4,8 @@ import 'dart:io'; import 'package:dio/dio.dart' hide Response; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/activity.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/core/models/activity.dart'; +import 'package:island/core/network.dart'; import 'package:island/talker.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shelf/shelf.dart'; diff --git a/lib/pods/activity/activity_rpc.g.dart b/lib/activity/activity_rpc.g.dart similarity index 100% rename from lib/pods/activity/activity_rpc.g.dart rename to lib/activity/activity_rpc.g.dart diff --git a/lib/pods/activity/ipc_server.dart b/lib/activity/ipc_server.dart similarity index 100% rename from lib/pods/activity/ipc_server.dart rename to lib/activity/ipc_server.dart diff --git a/lib/pods/activity/ipc_server.web.dart b/lib/activity/ipc_server.web.dart similarity index 100% rename from lib/pods/activity/ipc_server.web.dart rename to lib/activity/ipc_server.web.dart diff --git a/lib/models/auth.dart b/lib/auth/auth_models/auth.dart similarity index 100% rename from lib/models/auth.dart rename to lib/auth/auth_models/auth.dart diff --git a/lib/models/auth.freezed.dart b/lib/auth/auth_models/auth.freezed.dart similarity index 100% rename from lib/models/auth.freezed.dart rename to lib/auth/auth_models/auth.freezed.dart diff --git a/lib/models/auth.g.dart b/lib/auth/auth_models/auth.g.dart similarity index 100% rename from lib/models/auth.g.dart rename to lib/auth/auth_models/auth.g.dart diff --git a/lib/screens/auth/captcha.config.dart b/lib/auth/captcha.config.dart similarity index 87% rename from lib/screens/auth/captcha.config.dart rename to lib/auth/captcha.config.dart index c522e189..a80a5fa3 100644 --- a/lib/screens/auth/captcha.config.dart +++ b/lib/auth/captcha.config.dart @@ -1,4 +1,4 @@ -import 'package:island/pods/network.dart'; +import 'package:island/core/network.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'captcha.config.g.dart'; diff --git a/lib/screens/auth/captcha.config.g.dart b/lib/auth/captcha.config.g.dart similarity index 100% rename from lib/screens/auth/captcha.config.g.dart rename to lib/auth/captcha.config.g.dart diff --git a/lib/screens/auth/captcha.dart b/lib/auth/captcha.dart similarity index 100% rename from lib/screens/auth/captcha.dart rename to lib/auth/captcha.dart diff --git a/lib/screens/auth/captcha.native.dart b/lib/auth/captcha.native.dart similarity index 92% rename from lib/screens/auth/captcha.native.dart rename to lib/auth/captcha.native.dart index 44591200..03122e17 100644 --- a/lib/screens/auth/captcha.native.dart +++ b/lib/auth/captcha.native.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:island/screens/auth/captcha.config.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/auth/captcha.config.dart'; +import 'package:island/core/widgets/content/sheet.dart'; class CaptchaScreen extends ConsumerWidget { static Future show(BuildContext context) { diff --git a/lib/screens/auth/captcha.web.dart b/lib/auth/captcha.web.dart similarity index 93% rename from lib/screens/auth/captcha.web.dart rename to lib/auth/captcha.web.dart index 69c938b5..754513d6 100644 --- a/lib/screens/auth/captcha.web.dart +++ b/lib/auth/captcha.web.dart @@ -1,8 +1,8 @@ import 'dart:ui_web' as ui; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/screens/auth/captcha.config.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/core/config.dart'; +import 'package:island/auth/captcha.config.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:web/web.dart' as web; import 'package:flutter/material.dart'; diff --git a/lib/screens/auth/create_account.dart b/lib/auth/create_account.dart similarity index 90% rename from lib/screens/auth/create_account.dart rename to lib/auth/create_account.dart index 57ff2674..17a96ab5 100644 --- a/lib/screens/auth/create_account.dart +++ b/lib/auth/create_account.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; import 'create_account_content.dart'; diff --git a/lib/screens/auth/create_account_content.dart b/lib/auth/create_account_content.dart similarity index 90% rename from lib/screens/auth/create_account_content.dart rename to lib/auth/create_account_content.dart index b82984c7..fc8f609e 100644 --- a/lib/screens/auth/create_account_content.dart +++ b/lib/auth/create_account_content.dart @@ -9,14 +9,14 @@ import 'package:go_router/go_router.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:gap/gap.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/pods/websocket.dart'; -import 'package:island/services/event_bus.dart'; -import 'package:island/services/notify.dart'; -import 'package:island/services/udid.dart'; -import 'package:island/widgets/alert.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/core/websocket.dart'; +import 'package:island/core/services/event_bus.dart'; +import 'package:island/core/services/notify.dart'; +import 'package:island/core/services/udid.dart'; +import 'package:island/shared/widgets/alert.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -42,8 +42,9 @@ Widget getProviderIcon(String provider, {double size = 24, Color? color}) { 'assets/images/oidc/$providerLower.svg', width: size, height: size, - colorFilter: - color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color, BlendMode.srcIn) + : null, ); case 'spotify': return Image.asset( @@ -844,10 +845,9 @@ class CreateAccountContent extends HookConsumerWidget { .toString(); final isLaunched = await launchUrlString( url, - mode: - kIsWeb - ? LaunchMode.platformDefault - : LaunchMode.externalApplication, + mode: kIsWeb + ? LaunchMode.platformDefault + : LaunchMode.externalApplication, ); if (!isLaunched) { waitingForOidc.value = false; @@ -875,10 +875,10 @@ class CreateAccountContent extends HookConsumerWidget { value: period.value / 5, ), Expanded( - child: - SingleChildScrollView( - child: PageTransitionSwitcher( - transitionBuilder: ( + child: SingleChildScrollView( + child: PageTransitionSwitcher( + transitionBuilder: + ( Widget child, Animation primaryAnimation, Animation secondaryAnimation, @@ -893,51 +893,51 @@ class CreateAccountContent extends HookConsumerWidget { ), ); }, - child: switch (period.value % 5) { - 1 => _CreateAccountPasswordScreen( - key: const ValueKey(1), - passwordController: passwordController, - onNext: () => period.value++, - onBack: () => period.value--, - onBusy: (value) => isBusy.value = value, - ), - 2 => _CreateAccountProfileScreen( - key: const ValueKey(2), - usernameController: usernameController, - nicknameController: nicknameController, - isOidcFlow: onboardingToken.value != null, - onNext: () => period.value++, - onBack: () => period.value--, - onBusy: (value) => isBusy.value = value, - ), - 3 => _CreateAccountTermsScreen( - key: const ValueKey(3), - onNext: () => period.value++, - onBack: () => period.value--, - onBusy: (value) => isBusy.value = value, - ), - 4 => _CreateAccountCompleteScreen( - key: const ValueKey(4), - emailController: emailController, - passwordController: passwordController, - usernameController: usernameController, - nicknameController: nicknameController, - affiliationSpellController: affiliationSpellController, - onboardingToken: onboardingToken.value, - onBack: () => period.value--, - onBusy: (value) => isBusy.value = value, - ), - _ => _CreateAccountEmailScreen( - key: const ValueKey(0), - emailController: emailController, - affiliationSpellController: affiliationSpellController, - onNext: () => period.value++, - onBusy: (value) => isBusy.value = value, - onOidc: withOidc, - ), - }, - ).padding(all: 24), - ).center(), + child: switch (period.value % 5) { + 1 => _CreateAccountPasswordScreen( + key: const ValueKey(1), + passwordController: passwordController, + onNext: () => period.value++, + onBack: () => period.value--, + onBusy: (value) => isBusy.value = value, + ), + 2 => _CreateAccountProfileScreen( + key: const ValueKey(2), + usernameController: usernameController, + nicknameController: nicknameController, + isOidcFlow: onboardingToken.value != null, + onNext: () => period.value++, + onBack: () => period.value--, + onBusy: (value) => isBusy.value = value, + ), + 3 => _CreateAccountTermsScreen( + key: const ValueKey(3), + onNext: () => period.value++, + onBack: () => period.value--, + onBusy: (value) => isBusy.value = value, + ), + 4 => _CreateAccountCompleteScreen( + key: const ValueKey(4), + emailController: emailController, + passwordController: passwordController, + usernameController: usernameController, + nicknameController: nicknameController, + affiliationSpellController: affiliationSpellController, + onboardingToken: onboardingToken.value, + onBack: () => period.value--, + onBusy: (value) => isBusy.value = value, + ), + _ => _CreateAccountEmailScreen( + key: const ValueKey(0), + emailController: emailController, + affiliationSpellController: affiliationSpellController, + onNext: () => period.value++, + onBusy: (value) => isBusy.value = value, + onOidc: withOidc, + ), + }, + ).padding(all: 24), + ).center(), ), const Gap(4), ], diff --git a/lib/screens/auth/create_account_modal.dart b/lib/auth/create_account_modal.dart similarity index 89% rename from lib/screens/auth/create_account_modal.dart rename to lib/auth/create_account_modal.dart index ba5f0303..c455110c 100644 --- a/lib/screens/auth/create_account_modal.dart +++ b/lib/auth/create_account_modal.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'create_account_content.dart'; diff --git a/lib/screens/auth/login.dart b/lib/auth/login.dart similarity index 94% rename from lib/screens/auth/login.dart rename to lib/auth/login.dart index 8a6a8b69..dbcd25fb 100644 --- a/lib/screens/auth/login.dart +++ b/lib/auth/login.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'login_content.dart'; diff --git a/lib/screens/auth/login_content.dart b/lib/auth/login_content.dart similarity index 87% rename from lib/screens/auth/login_content.dart rename to lib/auth/login_content.dart index 1dca2855..f102376c 100644 --- a/lib/screens/auth/login_content.dart +++ b/lib/auth/login_content.dart @@ -9,16 +9,16 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:gap/gap.dart'; -import 'package:island/models/auth.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/pods/websocket.dart'; -import 'package:island/screens/account/me/settings_connections.dart'; -import 'package:island/services/event_bus.dart'; -import 'package:island/services/notify.dart'; -import 'package:island/services/udid.dart'; -import 'package:island/widgets/alert.dart'; +import 'package:island/auth/auth_models/auth.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/core/websocket.dart'; +import 'package:island/accounts/account/me/settings_connections.dart'; +import 'package:island/core/services/event_bus.dart'; +import 'package:island/core/services/notify.dart'; +import 'package:island/core/services/udid.dart'; +import 'package:island/shared/widgets/alert.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:sign_in_with_apple/sign_in_with_apple.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -274,10 +274,10 @@ class LoginContent extends HookConsumerWidget { else const Gap(4), Expanded( - child: - SingleChildScrollView( - child: PageTransitionSwitcher( - transitionBuilder: ( + child: SingleChildScrollView( + child: PageTransitionSwitcher( + transitionBuilder: + ( Widget child, Animation primaryAnimation, Animation secondaryAnimation, @@ -292,41 +292,39 @@ class LoginContent extends HookConsumerWidget { ), ); }, - child: switch (period.value % 3) { - 1 => _LoginPickerScreen( - key: const ValueKey(1), - challenge: currentTicket.value, - factors: factors.value, - onChallenge: - (SnAuthChallenge? p0) => currentTicket.value = p0, - onPickFactor: - (SnAuthFactor p0) => factorPicked.value = p0, - onNext: () => period.value++, - onBusy: (value) => isBusy.value = value, - ), - 2 => _LoginCheckScreen( - key: const ValueKey(2), - challenge: currentTicket.value, - factor: factorPicked.value, - onChallenge: - (SnAuthChallenge? p0) => currentTicket.value = p0, - onNext: () => period.value = 1, - onBusy: (value) => isBusy.value = value, - ), - _ => _LoginLookupScreen( - key: const ValueKey(0), - ticket: currentTicket.value, - onChallenge: - (SnAuthChallenge? p0) => currentTicket.value = p0, - onFactor: - (List? p0) => - factors.value = p0 ?? [], - onNext: () => period.value++, - onBusy: (value) => isBusy.value = value, - ), - }, - ).padding(all: 24), - ).center(), + child: switch (period.value % 3) { + 1 => _LoginPickerScreen( + key: const ValueKey(1), + challenge: currentTicket.value, + factors: factors.value, + onChallenge: (SnAuthChallenge? p0) => + currentTicket.value = p0, + onPickFactor: (SnAuthFactor p0) => factorPicked.value = p0, + onNext: () => period.value++, + onBusy: (value) => isBusy.value = value, + ), + 2 => _LoginCheckScreen( + key: const ValueKey(2), + challenge: currentTicket.value, + factor: factorPicked.value, + onChallenge: (SnAuthChallenge? p0) => + currentTicket.value = p0, + onNext: () => period.value = 1, + onBusy: (value) => isBusy.value = value, + ), + _ => _LoginLookupScreen( + key: const ValueKey(0), + ticket: currentTicket.value, + onChallenge: (SnAuthChallenge? p0) => + currentTicket.value = p0, + onFactor: (List? p0) => + factors.value = p0 ?? [], + onNext: () => period.value++, + onBusy: (value) => isBusy.value = value, + ), + }, + ).padding(all: 24), + ).center(), ), const Gap(4), @@ -388,10 +386,9 @@ class _LoginPickerScreen extends HookConsumerWidget { try { await client.post( '/pass/auth/challenge/${challenge!.id}/factors/${factorPicked.value!.id}', - data: - hintController.text.isNotEmpty - ? jsonEncode(hintController.text) - : null, + data: hintController.text.isNotEmpty + ? jsonEncode(hintController.text) + : null, ); onPickFactor(factors!.where((x) => x == factorPicked.value).first); onNext(); @@ -581,17 +578,16 @@ class _LoginLookupScreen extends HookConsumerWidget { 'account': uname, 'device_id': await getUdid(), 'device_name': await getDeviceName(), - 'platform': - kIsWeb - ? 1 - : switch (defaultTargetPlatform) { - TargetPlatform.iOS => 2, - TargetPlatform.android => 3, - TargetPlatform.macOS => 4, - TargetPlatform.windows => 5, - TargetPlatform.linux => 6, - _ => 0, - }, + 'platform': kIsWeb + ? 1 + : switch (defaultTargetPlatform) { + TargetPlatform.iOS => 2, + TargetPlatform.android => 3, + TargetPlatform.macOS => 4, + TargetPlatform.windows => 5, + TargetPlatform.linux => 6, + _ => 0, + }, }, ); final result = SnAuthChallenge.fromJson(resp.data); @@ -663,18 +659,17 @@ class _LoginLookupScreen extends HookConsumerWidget { if (token?.token != null) { queryParams['token'] = token!.token; } - final url = - Uri.parse( - '$serverUrl/pass/auth/login/${provider.toLowerCase()}', - ).replace(queryParameters: queryParams).toString(); + final url = Uri.parse( + '$serverUrl/pass/auth/login/${provider.toLowerCase()}', + ).replace(queryParameters: queryParams).toString(); final isLaunched = await launchUrlString( url, - mode: - kIsWeb - ? LaunchMode.platformDefault - : LaunchMode.externalApplication, - webOnlyWindowName: - token?.token != null ? 'auth-${token!.token}' : 'auth', + mode: kIsWeb + ? LaunchMode.platformDefault + : LaunchMode.externalApplication, + webOnlyWindowName: token?.token != null + ? 'auth-${token!.token}' + : 'auth', ); if (!isLaunched) { waitingForOidc.value = false; diff --git a/lib/screens/auth/login_modal.dart b/lib/auth/login_modal.dart similarity index 88% rename from lib/screens/auth/login_modal.dart rename to lib/auth/login_modal.dart index 334a4844..44807e65 100644 --- a/lib/screens/auth/login_modal.dart +++ b/lib/auth/login_modal.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'login_content.dart'; diff --git a/lib/screens/auth/oidc.dart b/lib/auth/oidc.dart similarity index 100% rename from lib/screens/auth/oidc.dart rename to lib/auth/oidc.dart diff --git a/lib/screens/auth/oidc.native.dart b/lib/auth/oidc.native.dart similarity index 76% rename from lib/screens/auth/oidc.native.dart rename to lib/auth/oidc.native.dart index 311b1250..9317569a 100644 --- a/lib/screens/auth/oidc.native.dart +++ b/lib/auth/oidc.native.dart @@ -7,11 +7,11 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:gap/gap.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/services/udid.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/udid.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; import 'package:styled_widget/styled_widget.dart'; class OidcScreen extends ConsumerStatefulWidget { @@ -70,14 +70,13 @@ class _OidcScreenState extends ConsumerState { Expanded( child: InAppWebView( initialSettings: InAppWebViewSettings( - userAgent: - kIsWeb - ? null - : Platform.isIOS - ? 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1' - : Platform.isAndroid - ? 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36' - : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', + userAgent: kIsWeb + ? null + : Platform.isIOS + ? 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1' + : Platform.isAndroid + ? 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36' + : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', ), initialUrlRequest: URLRequest( url: WebUri( @@ -106,32 +105,30 @@ class _OidcScreenState extends ConsumerState { }, ); }, - shouldOverrideUrlLoading: ( - controller, - navigationAction, - ) async { - final url = navigationAction.request.url; - if (url != null) { - setState(() { - currentUrl = url.toString(); - _urlController.text = currentUrl ?? ''; - _isLoading = true; - }); + shouldOverrideUrlLoading: + (controller, navigationAction) async { + final url = navigationAction.request.url; + if (url != null) { + setState(() { + currentUrl = url.toString(); + _urlController.text = currentUrl ?? ''; + _isLoading = true; + }); - final path = url.path; - final queryParams = url.queryParameters; + final path = url.path; + final queryParams = url.queryParameters; - // Check if we're on the token page - if (path.endsWith('/auth/callback')) { - // Extract token from URL - final challenge = queryParams['challenge']; - // Return the token and close the webview - Navigator.of(context).pop(challenge); - return NavigationActionPolicy.CANCEL; - } - } - return NavigationActionPolicy.ALLOW; - }, + // Check if we're on the token page + if (path.endsWith('/auth/callback')) { + // Extract token from URL + final challenge = queryParams['challenge']; + // Return the token and close the webview + Navigator.of(context).pop(challenge); + return NavigationActionPolicy.CANCEL; + } + } + return NavigationActionPolicy.ALLOW; + }, onUpdateVisitedHistory: (controller, url, androidIsReload) { if (url != null) { setState(() { diff --git a/lib/screens/auth/oidc.web.dart b/lib/auth/oidc.web.dart similarity index 74% rename from lib/screens/auth/oidc.web.dart rename to lib/auth/oidc.web.dart index c27e3de2..1d3e7a75 100644 --- a/lib/screens/auth/oidc.web.dart +++ b/lib/auth/oidc.web.dart @@ -3,9 +3,9 @@ import 'dart:ui_web' as ui; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; import 'package:web/web.dart' as web; import 'package:flutter/material.dart'; @@ -38,15 +38,13 @@ class _OidcScreenState extends ConsumerState { // Create the iframe for the OIDC login final token = ref.watch(tokenProvider); - final iframe = - web.HTMLIFrameElement() - ..src = - (token?.token.isNotEmpty ?? false) - ? '$serverUrl/auth/login/${widget.provider}?tk=${token!.token}' - : '$serverUrl/auth/login/${widget.provider}' - ..style.border = 'none' - ..width = '100%' - ..height = '100%'; + final iframe = web.HTMLIFrameElement() + ..src = (token?.token.isNotEmpty ?? false) + ? '$serverUrl/auth/login/${widget.provider}?tk=${token!.token}' + : '$serverUrl/auth/login/${widget.provider}' + ..style.border = 'none' + ..width = '100%' + ..height = '100%'; // Add the iframe to the document body web.document.body!.append(iframe); @@ -77,10 +75,9 @@ class _OidcScreenState extends ConsumerState { appBar: AppBar( title: widget.title != null ? Text(widget.title!) : Text('login').tr(), ), - body: - _isInitialized - ? HtmlElementView(viewType: _viewType) - : Center(child: CircularProgressIndicator()), + body: _isInitialized + ? HtmlElementView(viewType: _viewType) + : Center(child: CircularProgressIndicator()), ); } } diff --git a/lib/pods/web_auth/web_auth_providers.dart b/lib/auth/web_auth/web_auth_providers.dart similarity index 100% rename from lib/pods/web_auth/web_auth_providers.dart rename to lib/auth/web_auth/web_auth_providers.dart diff --git a/lib/pods/web_auth/web_auth_server.dart b/lib/auth/web_auth/web_auth_server.dart similarity index 99% rename from lib/pods/web_auth/web_auth_server.dart rename to lib/auth/web_auth/web_auth_server.dart index f7976ee6..28077a0b 100644 --- a/lib/pods/web_auth/web_auth_server.dart +++ b/lib/auth/web_auth/web_auth_server.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'dart:math'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:dio/dio.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/core/network.dart'; import 'package:island/talker.dart'; class WebAuthServer { diff --git a/lib/models/chat.dart b/lib/chat/chat_models/chat.dart similarity index 96% rename from lib/models/chat.dart rename to lib/chat/chat_models/chat.dart index 1c031156..4ea38671 100644 --- a/lib/models/chat.dart +++ b/lib/chat/chat_models/chat.dart @@ -1,7 +1,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/models/account.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/accounts/accounts_models/account.dart'; part 'chat.freezed.dart'; part 'chat.g.dart'; diff --git a/lib/models/chat.freezed.dart b/lib/chat/chat_models/chat.freezed.dart similarity index 100% rename from lib/models/chat.freezed.dart rename to lib/chat/chat_models/chat.freezed.dart diff --git a/lib/models/chat.g.dart b/lib/chat/chat_models/chat.g.dart similarity index 100% rename from lib/models/chat.g.dart rename to lib/chat/chat_models/chat.g.dart diff --git a/lib/pods/chat/chat_online_count.dart b/lib/chat/chat_online_count.dart similarity index 90% rename from lib/pods/chat/chat_online_count.dart rename to lib/chat/chat_online_count.dart index 7c7bd974..d98ace45 100644 --- a/lib/pods/chat/chat_online_count.dart +++ b/lib/chat/chat_online_count.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/websocket.dart'; -import 'package:island/models/account.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/websocket.dart'; +import 'package:island/accounts/accounts_models/account.dart'; part 'chat_online_count.g.dart'; diff --git a/lib/pods/chat/chat_online_count.g.dart b/lib/chat/chat_online_count.g.dart similarity index 100% rename from lib/pods/chat/chat_online_count.g.dart rename to lib/chat/chat_online_count.g.dart diff --git a/lib/pods/chat/call.dart b/lib/chat/chat_pod/call.dart similarity index 98% rename from lib/pods/chat/call.dart rename to lib/chat/chat_pod/call.dart index 09f585d4..d2bf6b01 100644 --- a/lib/pods/chat/call.dart +++ b/lib/chat/chat_pod/call.dart @@ -3,13 +3,13 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/chat/call_button.dart'; +import 'package:island/chat/chat_widgets/call_button.dart'; +import 'package:island/shared/widgets/alert.dart'; import 'package:livekit_client/livekit_client.dart' as lk; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/models/chat.dart'; +import 'package:island/core/network.dart'; +import 'package:island/chat/chat_models/chat.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:island/talker.dart'; diff --git a/lib/pods/chat/call.freezed.dart b/lib/chat/chat_pod/call.freezed.dart similarity index 100% rename from lib/pods/chat/call.freezed.dart rename to lib/chat/chat_pod/call.freezed.dart diff --git a/lib/pods/chat/call.g.dart b/lib/chat/chat_pod/call.g.dart similarity index 100% rename from lib/pods/chat/call.g.dart rename to lib/chat/chat_pod/call.g.dart diff --git a/lib/chat/chat_pod/chat_online_count.dart b/lib/chat/chat_pod/chat_online_count.dart new file mode 100644 index 00000000..d98ace45 --- /dev/null +++ b/lib/chat/chat_pod/chat_online_count.dart @@ -0,0 +1,50 @@ +import 'dart:async'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/websocket.dart'; +import 'package:island/accounts/accounts_models/account.dart'; + +part 'chat_online_count.g.dart'; + +@riverpod +class ChatOnlineCountNotifier extends _$ChatOnlineCountNotifier { + @override + Future build(String chatroomId) async { + final apiClient = ref.watch(apiClientProvider); + final ws = ref.watch(websocketProvider); + + // Fetch initial online count + final response = await apiClient.get( + '/messager/chat/$chatroomId/members/online', + ); + final initialCount = response.data as int; + + // Listen for websocket status updates + final subscription = ws.dataStream.listen((WebSocketPacket packet) { + if (packet.type == 'accounts.status.update') { + final data = packet.data; + if (data != null && data['chat_room_id'] == chatroomId) { + final status = SnAccountStatus.fromJson(data['status']); + var delta = status.isOnline ? 1 : -1; + if (status.clearedAt != null && + status.clearedAt!.isBefore(DateTime.now())) { + if (status.isInvisible) delta = 1; + } + // Update count based on online status + state.whenData((currentCount) { + final newCount = currentCount + delta; + state = AsyncData( + newCount.clamp(0, double.infinity).toInt(), + ); // Ensure non-negative + }); + } + } + }); + + ref.onDispose(() { + subscription.cancel(); + }); + + return initialCount; + } +} diff --git a/lib/chat/chat_pod/chat_online_count.g.dart b/lib/chat/chat_pod/chat_online_count.g.dart new file mode 100644 index 00000000..9ca25eb7 --- /dev/null +++ b/lib/chat/chat_pod/chat_online_count.g.dart @@ -0,0 +1,101 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_online_count.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ChatOnlineCountNotifier) +final chatOnlineCountProvider = ChatOnlineCountNotifierFamily._(); + +final class ChatOnlineCountNotifierProvider + extends $AsyncNotifierProvider { + ChatOnlineCountNotifierProvider._({ + required ChatOnlineCountNotifierFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'chatOnlineCountProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$chatOnlineCountNotifierHash(); + + @override + String toString() { + return r'chatOnlineCountProvider' + '' + '($argument)'; + } + + @$internal + @override + ChatOnlineCountNotifier create() => ChatOnlineCountNotifier(); + + @override + bool operator ==(Object other) { + return other is ChatOnlineCountNotifierProvider && + other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$chatOnlineCountNotifierHash() => + r'b2f9f17bfece1937ec90590b8f11db2bec923156'; + +final class ChatOnlineCountNotifierFamily extends $Family + with + $ClassFamilyOverride< + ChatOnlineCountNotifier, + AsyncValue, + int, + FutureOr, + String + > { + ChatOnlineCountNotifierFamily._() + : super( + retry: null, + name: r'chatOnlineCountProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ChatOnlineCountNotifierProvider call(String chatroomId) => + ChatOnlineCountNotifierProvider._(argument: chatroomId, from: this); + + @override + String toString() => r'chatOnlineCountProvider'; +} + +abstract class _$ChatOnlineCountNotifier extends $AsyncNotifier { + late final _$args = ref.$arg as String; + String get chatroomId => _$args; + + FutureOr build(String chatroomId); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, int>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, int>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} diff --git a/lib/pods/chat/chat_room.dart b/lib/chat/chat_pod/chat_room.dart similarity index 97% rename from lib/pods/chat/chat_room.dart rename to lib/chat/chat_pod/chat_room.dart index 96c491da..9b4841c2 100644 --- a/lib/pods/chat/chat_room.dart +++ b/lib/chat/chat_pod/chat_room.dart @@ -1,13 +1,13 @@ import 'package:dio/dio.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/database/drift_db.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/pods/database.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; +import 'package:island/data/drift_db.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/core/database.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'chat_room.g.dart'; diff --git a/lib/pods/chat/chat_room.g.dart b/lib/chat/chat_pod/chat_room.g.dart similarity index 100% rename from lib/pods/chat/chat_room.g.dart rename to lib/chat/chat_pod/chat_room.g.dart diff --git a/lib/pods/chat/chat_subscribe.dart b/lib/chat/chat_pod/chat_subscribe.dart similarity index 96% rename from lib/pods/chat/chat_subscribe.dart rename to lib/chat/chat_pod/chat_subscribe.dart index a1c7f3e3..539c6508 100644 --- a/lib/pods/chat/chat_subscribe.dart +++ b/lib/chat/chat_pod/chat_subscribe.dart @@ -2,15 +2,15 @@ import "dart:async"; import "dart:convert"; import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:island/chat/chat_widgets/call_button.dart"; +import "package:island/chat/messages_notifier.dart"; import "package:just_audio/just_audio.dart"; -import "package:island/pods/config.dart"; -import "package:island/models/chat.dart"; -import "package:island/pods/chat/chat_room.dart"; -import "package:island/pods/lifecycle.dart"; -import "package:island/pods/chat/messages_notifier.dart"; -import "package:island/pods/websocket.dart"; +import "package:island/core/config.dart"; +import "package:island/chat/chat_models/chat.dart"; +import "package:island/chat/chat_pod/chat_room.dart"; +import "package:island/core/lifecycle.dart"; +import "package:island/core/websocket.dart"; import "package:island/talker.dart"; -import "package:island/widgets/chat/call_button.dart"; import "package:riverpod_annotation/riverpod_annotation.dart"; part 'chat_subscribe.g.dart'; diff --git a/lib/pods/chat/chat_subscribe.g.dart b/lib/chat/chat_pod/chat_subscribe.g.dart similarity index 100% rename from lib/pods/chat/chat_subscribe.g.dart rename to lib/chat/chat_pod/chat_subscribe.g.dart diff --git a/lib/pods/chat/chat_summary.dart b/lib/chat/chat_pod/chat_summary.dart similarity index 95% rename from lib/pods/chat/chat_summary.dart rename to lib/chat/chat_pod/chat_summary.dart index d8b9ef79..4308033b 100644 --- a/lib/pods/chat/chat_summary.dart +++ b/lib/chat/chat_pod/chat_summary.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'dart:math' as math; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/websocket.dart'; -import 'package:island/pods/chat/chat_subscribe.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/websocket.dart'; +import 'package:island/chat/chat_pod/chat_subscribe.dart'; part 'chat_summary.g.dart'; diff --git a/lib/pods/chat/chat_summary.g.dart b/lib/chat/chat_pod/chat_summary.g.dart similarity index 100% rename from lib/pods/chat/chat_summary.g.dart rename to lib/chat/chat_pod/chat_summary.g.dart diff --git a/lib/chat/chat_room.dart b/lib/chat/chat_room.dart new file mode 100644 index 00000000..9b4841c2 --- /dev/null +++ b/lib/chat/chat_room.dart @@ -0,0 +1,452 @@ +import 'package:dio/dio.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/data/drift_db.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/core/database.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'chat_room.g.dart'; + +final chatSyncingProvider = NotifierProvider( + ChatSyncingNotifier.new, +); + +class ChatSyncingNotifier extends Notifier { + @override + bool build() => false; + + void set(bool value) => state = value; +} + +final flashingMessagesProvider = + NotifierProvider>( + FlashingMessagesNotifier.new, + ); + +class FlashingMessagesNotifier extends Notifier> { + @override + Set build() => {}; + + void update(Set Function(Set) cb) { + state = cb(state); + } + + void clear() => state = {}; +} + +@riverpod +class ChatRoomJoinedNotifier extends _$ChatRoomJoinedNotifier { + @override + Future> build() async { + final db = ref.watch(databaseProvider); + + try { + final localRoomsData = await db.select(db.chatRooms).get(); + final localRealmsData = await db.select(db.realms).get(); + if (localRoomsData.isNotEmpty) { + final localRooms = await Future.wait( + localRoomsData.map((row) async { + final membersRows = await (db.select( + db.chatMembers, + )..where((m) => m.chatRoomId.equals(row.id))).get(); + final members = membersRows.map((mRow) { + final account = SnAccount.fromJson(mRow.account); + return SnChatMember( + id: mRow.id, + chatRoomId: mRow.chatRoomId, + accountId: mRow.accountId, + account: account, + nick: mRow.nick, + notify: mRow.notify, + joinedAt: mRow.joinedAt, + breakUntil: mRow.breakUntil, + timeoutUntil: mRow.timeoutUntil, + status: null, + createdAt: mRow.createdAt, + updatedAt: mRow.updatedAt, + deletedAt: mRow.deletedAt, + chatRoom: null, + ); + }).toList(); + return SnChatRoom( + id: row.id, + name: row.name, + description: row.description, + type: row.type, + isPublic: row.isPublic!, + isCommunity: row.isCommunity!, + picture: row.picture != null + ? SnCloudFile.fromJson(row.picture!) + : null, + background: row.background != null + ? SnCloudFile.fromJson(row.background!) + : null, + realmId: row.realmId, + accountId: row.accountId, + realm: localRealmsData + .where((e) => e.id == row.realmId) + .map((e) => _buildRealmFromTableEntry(e)) + .firstOrNull, + createdAt: row.createdAt, + updatedAt: row.updatedAt, + deletedAt: row.deletedAt, + members: members, + isPinned: row.isPinned ?? false, + ); + }), + ); + + // Background sync + Future(() async { + try { + final client = ref.read(apiClientProvider); + final resp = await client.get('/messager/chat'); + final remoteRooms = resp.data + .map((e) => SnChatRoom.fromJson(e)) + .cast() + .toList(); + await db.saveChatRooms(remoteRooms, override: true); + // Update state with fresh data + state = AsyncData(await _buildRoomsFromDb(db)); + } catch (_) {} + }).ignore(); + + return localRooms; + } + } catch (_) {} + + // Fallback to API + final client = ref.watch(apiClientProvider); + final resp = await client.get('/messager/chat'); + final rooms = resp.data + .map((e) => SnChatRoom.fromJson(e)) + .cast() + .toList(); + await db.saveChatRooms(rooms, override: true); + return rooms; + } + + SnRealm _buildRealmFromTableEntry(Realm localRealm) { + return SnRealm( + id: localRealm.id, + slug: localRealm.slug, + name: localRealm.name ?? localRealm.slug, + description: localRealm.description ?? '', + verifiedAs: localRealm.verifiedAs, + verifiedAt: localRealm.verifiedAt, + isCommunity: localRealm.isCommunity, + isPublic: localRealm.isPublic, + picture: localRealm.picture != null + ? SnCloudFile.fromJson(localRealm.picture!) + : null, + background: localRealm.background != null + ? SnCloudFile.fromJson(localRealm.background!) + : null, + accountId: localRealm.accountId ?? '', + createdAt: localRealm.createdAt, + updatedAt: localRealm.updatedAt, + deletedAt: localRealm.deletedAt, + ); + } + + Future> _buildRoomsFromDb(AppDatabase db) async { + final localRoomsData = await db.select(db.chatRooms).get(); + return Future.wait( + localRoomsData.map((row) async { + final membersRows = await (db.select( + db.chatMembers, + )..where((m) => m.chatRoomId.equals(row.id))).get(); + final members = membersRows.map((mRow) { + final account = SnAccount.fromJson(mRow.account); + return SnChatMember( + id: mRow.id, + chatRoomId: mRow.chatRoomId, + accountId: mRow.accountId, + account: account, + nick: mRow.nick, + notify: mRow.notify, + joinedAt: mRow.joinedAt, + breakUntil: mRow.breakUntil, + timeoutUntil: mRow.timeoutUntil, + status: null, + createdAt: mRow.createdAt, + updatedAt: mRow.updatedAt, + deletedAt: mRow.deletedAt, + chatRoom: null, + ); + }).toList(); + + // Load realm if it exists + SnRealm? realm; + if (row.realmId != null) { + try { + final realmRow = await (db.select( + db.realms, + )..where((r) => r.id.equals(row.realmId!))).getSingleOrNull(); + if (realmRow != null) { + realm = SnRealm( + id: realmRow.id, + slug: '', // Not stored in DB + name: realmRow.name ?? '', + description: realmRow.description ?? '', + verifiedAs: null, // Not stored in DB + verifiedAt: null, // Not stored in DB + isCommunity: false, // Not stored in DB + isPublic: true, // Not stored in DB + picture: realmRow.picture != null + ? SnCloudFile.fromJson(realmRow.picture!) + : null, + background: realmRow.background != null + ? SnCloudFile.fromJson(realmRow.background!) + : null, + accountId: realmRow.accountId ?? '', + createdAt: realmRow.createdAt, + updatedAt: realmRow.updatedAt, + deletedAt: realmRow.deletedAt, + ); + } + } catch (_) { + // Realm not found, keep as null + } + } + + return SnChatRoom( + id: row.id, + name: row.name, + description: row.description, + type: row.type, + isPublic: row.isPublic!, + isCommunity: row.isCommunity!, + picture: row.picture != null + ? SnCloudFile.fromJson(row.picture!) + : null, + background: row.background != null + ? SnCloudFile.fromJson(row.background!) + : null, + realmId: row.realmId, + accountId: row.accountId, + realm: realm, + createdAt: row.createdAt, + updatedAt: row.updatedAt, + deletedAt: row.deletedAt, + members: members, + isPinned: row.isPinned ?? false, + ); + }), + ); + } +} + +@riverpod +class ChatRoomNotifier extends _$ChatRoomNotifier { + @override + Future build(String? identifier) async { + if (identifier == null) return null; + final db = ref.watch(databaseProvider); + + try { + // Try to get from local database first + final localRoomData = await (db.select( + db.chatRooms, + )..where((r) => r.id.equals(identifier))).getSingleOrNull(); + + if (localRoomData != null) { + // Fetch members for this room + final membersRows = await (db.select( + db.chatMembers, + )..where((m) => m.chatRoomId.equals(localRoomData.id))).get(); + final members = membersRows.map((mRow) { + final account = SnAccount.fromJson(mRow.account); + return SnChatMember( + id: mRow.id, + chatRoomId: mRow.chatRoomId, + accountId: mRow.accountId, + account: account, + nick: mRow.nick, + notify: mRow.notify, + joinedAt: mRow.joinedAt, + breakUntil: mRow.breakUntil, + timeoutUntil: mRow.timeoutUntil, + status: null, + createdAt: mRow.createdAt, + updatedAt: mRow.updatedAt, + deletedAt: mRow.deletedAt, + chatRoom: null, + ); + }).toList(); + + final localRoom = SnChatRoom( + id: localRoomData.id, + name: localRoomData.name, + description: localRoomData.description, + type: localRoomData.type, + isPublic: localRoomData.isPublic!, + isCommunity: localRoomData.isCommunity!, + picture: localRoomData.picture != null + ? SnCloudFile.fromJson(localRoomData.picture!) + : null, + background: localRoomData.background != null + ? SnCloudFile.fromJson(localRoomData.background!) + : null, + realmId: localRoomData.realmId, + accountId: localRoomData.accountId, + realm: null, + createdAt: localRoomData.createdAt, + updatedAt: localRoomData.updatedAt, + deletedAt: localRoomData.deletedAt, + members: members, + ); + + // Background sync + Future(() async { + try { + final client = ref.read(apiClientProvider); + final resp = await client.get('/messager/chat/$identifier'); + final remoteRoom = SnChatRoom.fromJson(resp.data); + // Update state with fresh data directly without saving to DB + // DB will be updated by ChatRoomJoinedNotifier's full sync + state = AsyncData(remoteRoom); + } catch (_) {} + }).ignore(); + + return localRoom; + } + } catch (_) {} + + // Fallback to API + try { + final client = ref.watch(apiClientProvider); + final resp = await client.get('/messager/chat/$identifier'); + final room = SnChatRoom.fromJson(resp.data); + await db.saveChatRooms([room]); + return room; + } catch (err) { + if (err is DioException && err.response?.statusCode == 404) { + return null; // Chat room not found + } + rethrow; // Rethrow other errors + } + } +} + +@riverpod +class ChatRoomIdentityNotifier extends _$ChatRoomIdentityNotifier { + @override + Future build(String? identifier) async { + if (identifier == null) return null; + final db = ref.watch(databaseProvider); + final userInfo = ref.watch(userInfoProvider); + + try { + // Try to get from local database first + if (userInfo.value != null) { + final localMemberData = + await (db.select(db.chatMembers) + ..where((m) => m.chatRoomId.equals(identifier)) + ..where((m) => m.accountId.equals(userInfo.value!.id))) + .getSingleOrNull(); + + if (localMemberData != null) { + final account = SnAccount.fromJson(localMemberData.account); + final localMember = SnChatMember( + id: localMemberData.id, + chatRoomId: localMemberData.chatRoomId, + accountId: localMemberData.accountId, + account: account, + nick: localMemberData.nick, + notify: localMemberData.notify, + joinedAt: localMemberData.joinedAt, + breakUntil: localMemberData.breakUntil, + timeoutUntil: localMemberData.timeoutUntil, + status: null, + createdAt: localMemberData.createdAt, + updatedAt: localMemberData.updatedAt, + deletedAt: localMemberData.deletedAt, + chatRoom: null, + ); + + // Background sync + Future(() async { + try { + final client = ref.read(apiClientProvider); + final resp = await client.get( + '/messager/chat/$identifier/members/me', + ); + final remoteMember = SnChatMember.fromJson(resp.data); + await db.saveMember(remoteMember); + // Update state with fresh data + if (userInfo.value != null) { + state = AsyncData( + await _buildMemberFromDb(db, identifier, userInfo.value!.id), + ); + } + } catch (_) {} + }).ignore(); + + return localMember; + } + } + } catch (_) {} + + // Fallback to API + try { + final client = ref.watch(apiClientProvider); + final resp = await client.get('/messager/chat/$identifier/members/me'); + final member = SnChatMember.fromJson(resp.data); + await db.saveMember(member); + return member; + } catch (err) { + if (err is DioException && err.response?.statusCode == 404) { + return null; // Chat member not found + } + rethrow; // Rethrow other errors + } + } + + Future _buildMemberFromDb( + AppDatabase db, + String identifier, + String accountId, + ) async { + final localMemberData = + await (db.select(db.chatMembers) + ..where((m) => m.chatRoomId.equals(identifier)) + ..where((m) => m.accountId.equals(accountId))) + .getSingleOrNull(); + + if (localMemberData == null) return null; + + final account = SnAccount.fromJson(localMemberData.account); + return SnChatMember( + id: localMemberData.id, + chatRoomId: localMemberData.chatRoomId, + accountId: localMemberData.accountId, + account: account, + nick: localMemberData.nick, + notify: localMemberData.notify, + joinedAt: localMemberData.joinedAt, + breakUntil: localMemberData.breakUntil, + timeoutUntil: localMemberData.timeoutUntil, + status: null, + createdAt: localMemberData.createdAt, + updatedAt: localMemberData.updatedAt, + deletedAt: localMemberData.deletedAt, + chatRoom: null, + ); + } +} + +@riverpod +Future> chatroomInvites(Ref ref) async { + final client = ref.watch(apiClientProvider); + final resp = await client.get('/messager/chat/invites'); + return resp.data + .map((e) => SnChatMember.fromJson(e)) + .cast() + .toList(); +} diff --git a/lib/chat/chat_room.g.dart b/lib/chat/chat_room.g.dart new file mode 100644 index 00000000..9d92068a --- /dev/null +++ b/lib/chat/chat_room.g.dart @@ -0,0 +1,279 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_room.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ChatRoomJoinedNotifier) +final chatRoomJoinedProvider = ChatRoomJoinedNotifierProvider._(); + +final class ChatRoomJoinedNotifierProvider + extends $AsyncNotifierProvider> { + ChatRoomJoinedNotifierProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'chatRoomJoinedProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$chatRoomJoinedNotifierHash(); + + @$internal + @override + ChatRoomJoinedNotifier create() => ChatRoomJoinedNotifier(); +} + +String _$chatRoomJoinedNotifierHash() => + r'b3726e10298b99a8529c5e28a5c402b95016f096'; + +abstract class _$ChatRoomJoinedNotifier + extends $AsyncNotifier> { + FutureOr> build(); + @$mustCallSuper + @override + void runBuild() { + final ref = + this.ref as $Ref>, List>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier>, List>, + AsyncValue>, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} + +@ProviderFor(ChatRoomNotifier) +final chatRoomProvider = ChatRoomNotifierFamily._(); + +final class ChatRoomNotifierProvider + extends $AsyncNotifierProvider { + ChatRoomNotifierProvider._({ + required ChatRoomNotifierFamily super.from, + required String? super.argument, + }) : super( + retry: null, + name: r'chatRoomProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$chatRoomNotifierHash(); + + @override + String toString() { + return r'chatRoomProvider' + '' + '($argument)'; + } + + @$internal + @override + ChatRoomNotifier create() => ChatRoomNotifier(); + + @override + bool operator ==(Object other) { + return other is ChatRoomNotifierProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$chatRoomNotifierHash() => r'9f7a8bdd4af381c6b60e65e74363a0af3c1a650e'; + +final class ChatRoomNotifierFamily extends $Family + with + $ClassFamilyOverride< + ChatRoomNotifier, + AsyncValue, + SnChatRoom?, + FutureOr, + String? + > { + ChatRoomNotifierFamily._() + : super( + retry: null, + name: r'chatRoomProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ChatRoomNotifierProvider call(String? identifier) => + ChatRoomNotifierProvider._(argument: identifier, from: this); + + @override + String toString() => r'chatRoomProvider'; +} + +abstract class _$ChatRoomNotifier extends $AsyncNotifier { + late final _$args = ref.$arg as String?; + String? get identifier => _$args; + + FutureOr build(String? identifier); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, SnChatRoom?>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, SnChatRoom?>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} + +@ProviderFor(ChatRoomIdentityNotifier) +final chatRoomIdentityProvider = ChatRoomIdentityNotifierFamily._(); + +final class ChatRoomIdentityNotifierProvider + extends $AsyncNotifierProvider { + ChatRoomIdentityNotifierProvider._({ + required ChatRoomIdentityNotifierFamily super.from, + required String? super.argument, + }) : super( + retry: null, + name: r'chatRoomIdentityProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$chatRoomIdentityNotifierHash(); + + @override + String toString() { + return r'chatRoomIdentityProvider' + '' + '($argument)'; + } + + @$internal + @override + ChatRoomIdentityNotifier create() => ChatRoomIdentityNotifier(); + + @override + bool operator ==(Object other) { + return other is ChatRoomIdentityNotifierProvider && + other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$chatRoomIdentityNotifierHash() => + r'1ce75462a19cc037c97ee6084a30fee1f5335875'; + +final class ChatRoomIdentityNotifierFamily extends $Family + with + $ClassFamilyOverride< + ChatRoomIdentityNotifier, + AsyncValue, + SnChatMember?, + FutureOr, + String? + > { + ChatRoomIdentityNotifierFamily._() + : super( + retry: null, + name: r'chatRoomIdentityProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ChatRoomIdentityNotifierProvider call(String? identifier) => + ChatRoomIdentityNotifierProvider._(argument: identifier, from: this); + + @override + String toString() => r'chatRoomIdentityProvider'; +} + +abstract class _$ChatRoomIdentityNotifier + extends $AsyncNotifier { + late final _$args = ref.$arg as String?; + String? get identifier => _$args; + + FutureOr build(String? identifier); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, SnChatMember?>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, SnChatMember?>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} + +@ProviderFor(chatroomInvites) +final chatroomInvitesProvider = ChatroomInvitesProvider._(); + +final class ChatroomInvitesProvider + extends + $FunctionalProvider< + AsyncValue>, + List, + FutureOr> + > + with + $FutureModifier>, + $FutureProvider> { + ChatroomInvitesProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'chatroomInvitesProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$chatroomInvitesHash(); + + @$internal + @override + $FutureProviderElement> $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr> create(Ref ref) { + return chatroomInvites(ref); + } +} + +String _$chatroomInvitesHash() => r'fc23231d5f111b1c3796ffae2b471384b951861a'; diff --git a/lib/chat/chat_subscribe.dart b/lib/chat/chat_subscribe.dart new file mode 100644 index 00000000..539c6508 --- /dev/null +++ b/lib/chat/chat_subscribe.dart @@ -0,0 +1,294 @@ +import "dart:async"; +import "dart:convert"; +import "package:flutter/material.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:island/chat/chat_widgets/call_button.dart"; +import "package:island/chat/messages_notifier.dart"; +import "package:just_audio/just_audio.dart"; +import "package:island/core/config.dart"; +import "package:island/chat/chat_models/chat.dart"; +import "package:island/chat/chat_pod/chat_room.dart"; +import "package:island/core/lifecycle.dart"; +import "package:island/core/websocket.dart"; +import "package:island/talker.dart"; +import "package:riverpod_annotation/riverpod_annotation.dart"; + +part 'chat_subscribe.g.dart'; + +final currentSubscribedChatIdProvider = + NotifierProvider( + CurrentSubscribedChatIdNotifier.new, + ); + +class CurrentSubscribedChatIdNotifier extends Notifier { + @override + String? build() => null; + + void set(String? value) => state = value; +} + +@riverpod +class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { + late SnChatRoom _chatRoom; + late SnChatMember _chatIdentity; + late MessagesNotifier _messagesNotifier; + + final List _typingStatuses = []; + Timer? _typingCleanupTimer; + Timer? _typingCooldownTimer; + Timer? _periodicSubscribeTimer; + StreamSubscription? _wsSubscription; + Function? _sendMessage; + + void _cleanupResources() { + if (_wsSubscription != null) { + _wsSubscription!.cancel(); + _wsSubscription = null; + } + if (_typingCleanupTimer != null) { + _typingCleanupTimer!.cancel(); + _typingCleanupTimer = null; + } + if (_periodicSubscribeTimer != null) { + _periodicSubscribeTimer!.cancel(); + _periodicSubscribeTimer = null; + } + } + + @override + List build(String roomId) { + final ws = ref.watch(websocketProvider); + final chatRoomAsync = ref.watch(chatRoomProvider(roomId)); + final chatIdentityAsync = ref.watch(chatRoomIdentityProvider(roomId)); + _messagesNotifier = ref.watch(messagesProvider(roomId).notifier); + + _cleanupResources(); + + if (chatRoomAsync.isLoading || chatIdentityAsync.isLoading) { + return []; + } + + if (chatRoomAsync.value == null || chatIdentityAsync.value == null) { + return []; + } + + _chatRoom = chatRoomAsync.value!; + _chatIdentity = chatIdentityAsync.value!; + + // Subscribe to messages + final wsState = ref.read(websocketStateProvider.notifier); + _sendMessage = wsState.sendMessage; + talker.info('[MessageSubscriber] Subscribing room $roomId'); + _sendMessage!( + jsonEncode( + WebSocketPacket( + type: 'messages.subscribe', + data: {'chat_room_id': roomId}, + endpoint: 'messager', + ), + ), + ); + + Future.microtask( + () => ref.read(currentSubscribedChatIdProvider.notifier).set(roomId), + ); + + // Send initial read receipt + sendReadReceipt(); + + // Set up WebSocket listener + _wsSubscription = ws.dataStream.listen(onMessage); + + // Set up typing status cleanup timer + _typingCleanupTimer = Timer.periodic(const Duration(seconds: 5), (_) { + if (_typingStatuses.isNotEmpty) { + // Remove typing statuses older than 5 seconds + final now = DateTime.now(); + _typingStatuses.removeWhere((member) { + final lastTyped = + member.lastTyped ?? + DateTime.now().subtract(const Duration(milliseconds: 1350)); + return now.difference(lastTyped).inSeconds > 5; + }); + state = List.of(_typingStatuses); + } + }); + + // Set up periodic subscribe timer (every 5 minutes) + _periodicSubscribeTimer = Timer.periodic(const Duration(minutes: 5), (_) { + _sendMessage!( + jsonEncode( + WebSocketPacket( + type: 'messages.subscribe', + data: {'chat_room_id': roomId}, + endpoint: 'messager', + ), + ), + ); + }); + + ref.listen(appLifecycleStateProvider, (previous, next) { + final lifecycleState = next.value; + if (lifecycleState == AppLifecycleState.paused || + lifecycleState == AppLifecycleState.inactive) { + // Unsubscribe when app goes to background + talker.info('[MessageSubscriber] Unsubscribing room $roomId'); + _sendMessage!( + jsonEncode( + WebSocketPacket( + type: 'messages.unsubscribe', + data: {'chat_room_id': roomId}, + endpoint: 'messager', + ), + ), + ); + } else if (lifecycleState == AppLifecycleState.resumed) { + // Resubscribe when app comes back to foreground + talker.info('[MessageSubscriber] Subscribing room $roomId'); + _sendMessage!( + jsonEncode( + WebSocketPacket( + type: 'messages.subscribe', + data: {'chat_room_id': roomId}, + endpoint: 'messager', + ), + ), + ); + } + }); + + final subscribedNotifier = ref.watch( + currentSubscribedChatIdProvider.notifier, + ); + + ref.onCancel(() { + talker.info('[MessageSubscriber] Unsubscribing room $roomId'); + subscribedNotifier.set(null); + try { + _sendMessage!( + jsonEncode( + WebSocketPacket( + type: 'messages.unsubscribe', + data: {'chat_room_id': roomId}, + endpoint: 'messager', + ), + ), + ); + } catch (e, stackTrace) { + talker.error( + '[MessageSubscriber] Error sending unsubscribe message for room $roomId: $e\n$stackTrace', + ); + } + try { + _cleanupResources(); + } catch (e, stackTrace) { + talker.error( + '[MessageSubscriber] Error during cleanup for room $roomId: $e\n$stackTrace', + ); + } + try { + if (_typingCooldownTimer != null) { + _typingCooldownTimer!.cancel(); + } + } catch (e, stackTrace) { + talker.error( + '[MessageSubscriber] Error cancelling typing cooldown timer for room $roomId: $e\n$stackTrace', + ); + } + }); + + return _typingStatuses; + } + + Future onMessage(WebSocketPacket pkt) async { + if (!pkt.type.startsWith('messages')) return; + if (['messages.read'].contains(pkt.type)) return; + + if (pkt.type == 'messages.typing' && pkt.data?['sender'] != null) { + if (pkt.data?['room_id'] != _chatRoom.id) return; + if (pkt.data?['sender_id'] == _chatIdentity.id) return; + + final sender = SnChatMember.fromJson( + pkt.data?['sender'], + ).copyWith(lastTyped: DateTime.now()); + + // Check if the sender is already in the typing list + final existingIndex = _typingStatuses.indexWhere( + (member) => member.id == sender.id, + ); + if (existingIndex >= 0) { + // Update the existing entry with new timestamp + _typingStatuses[existingIndex] = sender; + } else { + // Add new typing status + _typingStatuses.add(sender); + } + if (ref.mounted) state = List.of(_typingStatuses); + return; + } + + final message = SnChatMessage.fromJson(pkt.data!); + if (message.chatRoomId != _chatRoom.id) return; + switch (pkt.type) { + case 'messages.new': + case 'messages.update': + case 'messages.delete': + if (message.type.startsWith('call')) { + // Handle the ongoing call. + ref.invalidate(ongoingCallProvider(message.chatRoomId)); + } + _messagesNotifier.receiveMessage(message); + // Send read receipt for new message + sendReadReceipt(); + // Play sound for new messages when app is unfocused + if (pkt.type == 'messages.new' && + message.senderId != _chatIdentity.id && + ref.read(appLifecycleStateProvider).value != + AppLifecycleState.resumed && + ref.read(appSettingsProvider).soundEffects) { + final player = AudioPlayer(); + await player.setVolume(0.75); + await player.setAudioSource( + AudioSource.asset('assets/audio/messages.mp3'), + ); + await player.play(); + player.dispose(); + } + } + } + + void sendReadReceipt() { + // Send websocket packet + if (_sendMessage == null) return; + _sendMessage!( + jsonEncode( + WebSocketPacket( + type: 'messages.read', + data: {'chat_room_id': roomId}, + endpoint: 'messager', + ), + ), + ); + } + + void sendTypingStatus() { + // Don't send if we're already in a cooldown period + if (_typingCooldownTimer != null) return; + + // Send typing status immediately + if (_sendMessage == null) return; + _sendMessage!( + jsonEncode( + WebSocketPacket( + type: 'messages.typing', + data: {'chat_room_id': roomId}, + endpoint: 'messager', + ), + ), + ); + + _typingCooldownTimer = Timer(const Duration(milliseconds: 850), () { + _typingCooldownTimer = null; + }); + } +} diff --git a/lib/chat/chat_subscribe.g.dart b/lib/chat/chat_subscribe.g.dart new file mode 100644 index 00000000..6f1e7f36 --- /dev/null +++ b/lib/chat/chat_subscribe.g.dart @@ -0,0 +1,108 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_subscribe.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ChatSubscribeNotifier) +final chatSubscribeProvider = ChatSubscribeNotifierFamily._(); + +final class ChatSubscribeNotifierProvider + extends $NotifierProvider> { + ChatSubscribeNotifierProvider._({ + required ChatSubscribeNotifierFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'chatSubscribeProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$chatSubscribeNotifierHash(); + + @override + String toString() { + return r'chatSubscribeProvider' + '' + '($argument)'; + } + + @$internal + @override + ChatSubscribeNotifier create() => ChatSubscribeNotifier(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(List value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider>(value), + ); + } + + @override + bool operator ==(Object other) { + return other is ChatSubscribeNotifierProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$chatSubscribeNotifierHash() => + r'f2f5059a975fc44a41850459d6b7d041ff9d41cb'; + +final class ChatSubscribeNotifierFamily extends $Family + with + $ClassFamilyOverride< + ChatSubscribeNotifier, + List, + List, + List, + String + > { + ChatSubscribeNotifierFamily._() + : super( + retry: null, + name: r'chatSubscribeProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ChatSubscribeNotifierProvider call(String roomId) => + ChatSubscribeNotifierProvider._(argument: roomId, from: this); + + @override + String toString() => r'chatSubscribeProvider'; +} + +abstract class _$ChatSubscribeNotifier extends $Notifier> { + late final _$args = ref.$arg as String; + String get roomId => _$args; + + List build(String roomId); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, List>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, List>, + List, + Object?, + Object? + >; + element.handleCreate(ref, () => build(_$args)); + } +} diff --git a/lib/chat/chat_summary.dart b/lib/chat/chat_summary.dart new file mode 100644 index 00000000..4308033b --- /dev/null +++ b/lib/chat/chat_summary.dart @@ -0,0 +1,163 @@ +import 'dart:async'; +import 'dart:math' as math; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/websocket.dart'; +import 'package:island/chat/chat_pod/chat_subscribe.dart'; + +part 'chat_summary.g.dart'; + +@riverpod +class ChatUnreadCountNotifier extends _$ChatUnreadCountNotifier { + StreamSubscription? _subscription; + + @override + Future build() async { + // Subscribe to websocket events when this provider is built + _subscribeToWebSocket(); + + // Dispose the subscription when this provider is disposed + ref.onDispose(() { + _subscription?.cancel(); + }); + + try { + final client = ref.read(apiClientProvider); + final response = await client.get('/messager/chat/unread'); + return (response.data as num).toInt(); + } catch (_) { + return 0; + } + } + + void _subscribeToWebSocket() { + final webSocketService = ref.read(websocketProvider); + _subscription = webSocketService.dataStream.listen((packet) { + if (packet.type == 'messages.new' && packet.data != null) { + final message = SnChatMessage.fromJson(packet.data!); + final currentSubscribed = ref.read(currentSubscribedChatIdProvider); + // Only increment if the message is not from the currently subscribed chat + if (message.chatRoomId != currentSubscribed) { + _incrementCounter(); + } + } + }); + } + + Future _incrementCounter() async { + final current = await future; + state = AsyncData(current + 1); + } + + Future decrement(int count) async { + final current = await future; + state = AsyncData(math.max(current - count, 0)); + } + + void clear() async { + state = AsyncData(0); + } +} + +@Riverpod(keepAlive: true) +class ChatSummary extends _$ChatSummary { + @override + Future> build() async { + final client = ref.watch(apiClientProvider); + final resp = await client.get('/messager/chat/summary'); + + final Map data = resp.data; + final summaries = data.map( + (key, value) => MapEntry(key, SnChatSummary.fromJson(value)), + ); + + final ws = ref.watch(websocketProvider); + final subscription = ws.dataStream.listen((WebSocketPacket pkt) { + if (!pkt.type.startsWith('messages')) return; + if (pkt.type == 'messages.new') { + final message = SnChatMessage.fromJson(pkt.data!); + updateLastMessage(message.chatRoomId, message); + } else if (pkt.type == 'messages.update') { + final message = SnChatMessage.fromJson(pkt.data!); + updateMessageContent(message.chatRoomId, message); + } + }); + + ref.onDispose(() { + subscription.cancel(); + }); + + return summaries; + } + + Future clearUnreadCount(String chatId) async { + state.whenData((summaries) { + final summary = summaries[chatId]; + if (summary != null) { + // Decrement global unread count + final unreadToDecrement = summary.unreadCount; + if (unreadToDecrement > 0) { + ref + .read(chatUnreadCountProvider.notifier) + .decrement(unreadToDecrement); + } + + state = AsyncData({ + ...summaries, + chatId: SnChatSummary( + unreadCount: 0, + lastMessage: summary.lastMessage, + ), + }); + } + }); + } + + void updateLastMessage(String chatId, SnChatMessage message) { + state.whenData((summaries) { + final summary = summaries[chatId]; + if (summary != null) { + final currentSubscribed = ref.read(currentSubscribedChatIdProvider); + final increment = (chatId != currentSubscribed) ? 1 : 0; + state = AsyncData({ + ...summaries, + chatId: SnChatSummary( + unreadCount: summary.unreadCount + increment, + lastMessage: message, + ), + }); + } + }); + } + + void incrementUnreadCount(String chatId) { + state.whenData((summaries) { + final summary = summaries[chatId]; + if (summary != null) { + state = AsyncData({ + ...summaries, + chatId: SnChatSummary( + unreadCount: summary.unreadCount + 1, + lastMessage: summary.lastMessage, + ), + }); + } + }); + } + + void updateMessageContent(String chatId, SnChatMessage message) { + state.whenData((summaries) { + final summary = summaries[chatId]; + if (summary != null && summary.lastMessage?.id == message.id) { + state = AsyncData({ + ...summaries, + chatId: SnChatSummary( + unreadCount: summary.unreadCount, + lastMessage: message, + ), + }); + } + }); + } +} diff --git a/lib/chat/chat_summary.g.dart b/lib/chat/chat_summary.g.dart new file mode 100644 index 00000000..fee21e8a --- /dev/null +++ b/lib/chat/chat_summary.g.dart @@ -0,0 +1,108 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_summary.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ChatUnreadCountNotifier) +final chatUnreadCountProvider = ChatUnreadCountNotifierProvider._(); + +final class ChatUnreadCountNotifierProvider + extends $AsyncNotifierProvider { + ChatUnreadCountNotifierProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'chatUnreadCountProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$chatUnreadCountNotifierHash(); + + @$internal + @override + ChatUnreadCountNotifier create() => ChatUnreadCountNotifier(); +} + +String _$chatUnreadCountNotifierHash() => + r'169b28f8759ebd9de75f7de17f60d493737ee7a8'; + +abstract class _$ChatUnreadCountNotifier extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, int>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, int>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} + +@ProviderFor(ChatSummary) +final chatSummaryProvider = ChatSummaryProvider._(); + +final class ChatSummaryProvider + extends $AsyncNotifierProvider> { + ChatSummaryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'chatSummaryProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$chatSummaryHash(); + + @$internal + @override + ChatSummary create() => ChatSummary(); +} + +String _$chatSummaryHash() => r'82f516d4ce8b67dadb815523df57a3c30a33ef91'; + +abstract class _$ChatSummary + extends $AsyncNotifier> { + FutureOr> build(); + @$mustCallSuper + @override + void runBuild() { + final ref = + this.ref + as $Ref< + AsyncValue>, + Map + >; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier< + AsyncValue>, + Map + >, + AsyncValue>, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/lib/widgets/chat/call_button.dart b/lib/chat/chat_widgets/call_button.dart similarity index 94% rename from lib/widgets/chat/call_button.dart rename to lib/chat/chat_widgets/call_button.dart index 39117995..72aba3ee 100644 --- a/lib/widgets/chat/call_button.dart +++ b/lib/chat/chat_widgets/call_button.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/pods/chat/call.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/alert.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/chat/chat_pod/call.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'call_button.g.dart'; diff --git a/lib/widgets/chat/call_button.g.dart b/lib/chat/chat_widgets/call_button.g.dart similarity index 100% rename from lib/widgets/chat/call_button.g.dart rename to lib/chat/chat_widgets/call_button.g.dart diff --git a/lib/widgets/chat/call_content.dart b/lib/chat/chat_widgets/call_content.dart similarity index 97% rename from lib/widgets/chat/call_content.dart rename to lib/chat/chat_widgets/call_content.dart index 69f97684..c424a5e8 100644 --- a/lib/widgets/chat/call_content.dart +++ b/lib/chat/chat_widgets/call_content.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/chat/call.dart'; -import 'package:island/widgets/chat/call_participant_tile.dart'; +import 'package:island/chat/chat_pod/call.dart'; +import 'package:island/chat/chat_widgets/call_participant_tile.dart'; import 'package:livekit_client/livekit_client.dart'; class CallStageView extends HookConsumerWidget { diff --git a/lib/widgets/chat/call_overlay.dart b/lib/chat/chat_widgets/call_overlay.dart similarity index 97% rename from lib/widgets/chat/call_overlay.dart rename to lib/chat/chat_widgets/call_overlay.dart index f1a4485b..0148c905 100644 --- a/lib/widgets/chat/call_overlay.dart +++ b/lib/chat/chat_widgets/call_overlay.dart @@ -4,16 +4,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/pods/chat/call.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/screens/chat/call.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/chat/call_button.dart'; -import 'package:island/widgets/chat/call_content.dart'; -import 'package:island/widgets/chat/call_participant_tile.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/chat/chat_pod/call.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/chat/chat_widgets/call_button.dart'; +import 'package:island/chat/chat_widgets/call_content.dart'; +import 'package:island/chat/chat_widgets/call_participant_tile.dart'; +import 'package:island/chat/chat_widgets/call_screen.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:livekit_client/livekit_client.dart'; diff --git a/lib/widgets/chat/call_participant_card.dart b/lib/chat/chat_widgets/call_participant_card.dart similarity index 92% rename from lib/widgets/chat/call_participant_card.dart rename to lib/chat/chat_widgets/call_participant_card.dart index 56d19997..2d0349d5 100644 --- a/lib/widgets/chat/call_participant_card.dart +++ b/lib/chat/chat_widgets/call_participant_card.dart @@ -5,8 +5,8 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_popup_card/flutter_popup_card.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/chat/call.dart'; -import 'package:island/widgets/account/account_nameplate.dart'; +import 'package:island/accounts/accounts_widgets/account/account_nameplate.dart'; +import 'package:island/chat/chat_pod/call.dart'; import 'package:livekit_client/livekit_client.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -17,8 +17,9 @@ class CallParticipantCard extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final width = - math.min(MediaQuery.of(context).size.width - 80, 360).toDouble(); + final width = math + .min(MediaQuery.of(context).size.width - 80, 360) + .toDouble(); final callNotifier = ref.watch(callProvider.notifier); final volumeSliderValue = useState(callNotifier.getParticipantVolume(live)); @@ -88,10 +89,10 @@ class CallParticipantCard extends HookConsumerWidget { } } -class CallParticipantGestureDetector extends StatelessWidget { +class CallParticipantRegion extends StatelessWidget { final CallParticipantLive participant; final Widget child; - const CallParticipantGestureDetector({ + const CallParticipantRegion({ super.key, required this.participant, required this.child, diff --git a/lib/widgets/chat/call_participant_tile.dart b/lib/chat/chat_widgets/call_participant_tile.dart similarity index 96% rename from lib/widgets/chat/call_participant_tile.dart rename to lib/chat/chat_widgets/call_participant_tile.dart index 67310758..3ffba71e 100644 --- a/lib/widgets/chat/call_participant_tile.dart +++ b/lib/chat/chat_widgets/call_participant_tile.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/chat/call.dart'; -import 'package:island/screens/account/profile.dart'; -import 'package:island/widgets/chat/call_participant_card.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/accounts/account/profile.dart'; +import 'package:island/chat/chat_pod/call.dart'; +import 'package:island/chat/chat_widgets/call_participant_card.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:livekit_client/livekit_client.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -83,7 +83,7 @@ class SpeakingRippleAvatar extends HookConsumerWidget { alignment: Alignment.center, decoration: const BoxDecoration(shape: BoxShape.circle), child: account.when( - data: (value) => CallParticipantGestureDetector( + data: (value) => CallParticipantRegion( participant: live, child: ProfilePictureWidget( file: value.profile.picture, @@ -200,7 +200,7 @@ class CallParticipantTile extends HookConsumerWidget { else Center( child: account.when( - data: (value) => CallParticipantGestureDetector( + data: (value) => CallParticipantRegion( participant: live, child: ProfilePictureWidget( file: value.profile.picture, diff --git a/lib/screens/chat/call.dart b/lib/chat/chat_widgets/call_screen.dart similarity index 90% rename from lib/screens/chat/call.dart rename to lib/chat/chat_widgets/call_screen.dart index 0a8a6849..f1adc56d 100644 --- a/lib/screens/chat/call.dart +++ b/lib/chat/chat_widgets/call_screen.dart @@ -3,15 +3,15 @@ import 'package:flutter/material.dart' hide ConnectionState; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/pods/chat/call.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/chat/chat_pod/call.dart'; +import 'package:island/chat/chat_widgets/call_button.dart'; +import 'package:island/chat/chat_widgets/call_content.dart'; +import 'package:island/chat/chat_widgets/call_overlay.dart'; +import 'package:island/chat/chat_widgets/call_participant_tile.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; import 'package:island/talker.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/chat/call_button.dart'; -import 'package:island/widgets/chat/call_content.dart'; -import 'package:island/widgets/chat/call_overlay.dart'; -import 'package:island/widgets/chat/call_participant_tile.dart'; -import 'package:island/widgets/alert.dart'; import 'package:livekit_client/livekit_client.dart'; import 'package:material_symbols_icons/symbols.dart'; diff --git a/lib/screens/chat/room_detail.dart b/lib/chat/chat_widgets/chat_detail_screen.dart similarity index 96% rename from lib/screens/chat/room_detail.dart rename to lib/chat/chat_widgets/chat_detail_screen.dart index fbfca430..bbfe7dc7 100644 --- a/lib/screens/chat/room_detail.dart +++ b/lib/chat/chat_widgets/chat_detail_screen.dart @@ -1,31 +1,31 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:gap/gap.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/pods/chat/chat_room.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/widgets/account/account_pfc.dart'; -import 'package:island/widgets/account/account_picker.dart'; -import 'package:island/widgets/account/status.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/screens/chat/chat_form.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/accounts/accounts_widgets/account/account_pfc.dart'; +import 'package:island/accounts/accounts_widgets/account/account_picker.dart'; +import 'package:island/accounts/accounts_widgets/account/status.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/chat/chat_pod/chat_room.dart'; +import 'package:island/chat/chat_widgets/chat_room_form.dart'; +import 'package:island/chat/chat_widgets/chat_search_screen.dart'; +import 'package:island/core/database.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:island/pods/database.dart'; -import 'package:island/screens/chat/search_messages.dart'; -part 'room_detail.freezed.dart'; -part 'room_detail.g.dart'; +part 'chat_detail_screen.freezed.dart'; +part 'chat_detail_screen.g.dart'; @riverpod Future totalMessagesCount(Ref ref, String roomId) async { @@ -697,7 +697,7 @@ class _ChatMemberListSheet extends HookConsumerWidget { itemBuilder: (context, idx, member) { return ListTile( contentPadding: EdgeInsets.only(left: 16, right: 12), - leading: AccountPfcGestureDetector( + leading: AccountPfcRegion( uname: member.account.name, child: ProfilePictureWidget( file: member.account.profile.picture, diff --git a/lib/screens/chat/room_detail.freezed.dart b/lib/chat/chat_widgets/chat_detail_screen.freezed.dart similarity index 99% rename from lib/screens/chat/room_detail.freezed.dart rename to lib/chat/chat_widgets/chat_detail_screen.freezed.dart index 3b5cdfbd..ca2f82ce 100644 --- a/lib/screens/chat/room_detail.freezed.dart +++ b/lib/chat/chat_widgets/chat_detail_screen.freezed.dart @@ -3,7 +3,7 @@ // ignore_for_file: type=lint // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark -part of 'room_detail.dart'; +part of 'chat_detail_screen.dart'; // ************************************************************************** // FreezedGenerator diff --git a/lib/screens/chat/room_detail.g.dart b/lib/chat/chat_widgets/chat_detail_screen.g.dart similarity index 98% rename from lib/screens/chat/room_detail.g.dart rename to lib/chat/chat_widgets/chat_detail_screen.g.dart index 85725472..55fc7a21 100644 --- a/lib/screens/chat/room_detail.g.dart +++ b/lib/chat/chat_widgets/chat_detail_screen.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'room_detail.dart'; +part of 'chat_detail_screen.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/lib/widgets/chat/chat_input.dart b/lib/chat/chat_widgets/chat_input.dart similarity index 97% rename from lib/widgets/chat/chat_input.dart rename to lib/chat/chat_widgets/chat_input.dart index 165c1c6d..74a0aa6f 100644 --- a/lib/widgets/chat/chat_input.dart +++ b/lib/chat/chat_widgets/chat_input.dart @@ -7,30 +7,30 @@ import "package:flutter_typeahead/flutter_typeahead.dart"; import "package:gap/gap.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:image_picker/image_picker.dart"; -import "package:island/models/account.dart"; -import "package:island/models/autocomplete_response.dart"; -import "package:island/models/chat.dart"; -import "package:island/models/file.dart"; -import "package:island/models/poll.dart"; -import "package:island/models/publisher.dart"; -import "package:island/models/wallet.dart"; -import "package:island/models/realm.dart"; -import "package:island/models/sticker.dart"; -import "package:island/pods/config.dart"; -import "package:island/pods/userinfo.dart"; -import "package:island/services/autocomplete_service.dart"; -import "package:island/services/responsive.dart"; -import "package:island/widgets/content/attachment_preview.dart"; -import "package:island/widgets/content/cloud_files.dart"; -import "package:island/widgets/shared/upload_menu.dart"; +import "package:island/accounts/accounts_models/account.dart"; +import "package:island/discovery/discovery_models/autocomplete_response.dart"; +import "package:island/chat/chat_models/chat.dart"; +import "package:island/drive/drive_models/file.dart"; +import "package:island/posts/posts_models/poll.dart"; +import "package:island/posts/posts_models/publisher.dart"; +import "package:island/posts/posts_widgets/post/compose_fund.dart"; +import "package:island/posts/posts_widgets/post/compose_poll.dart"; +import "package:island/stickers/stickers_widgets/stickers/sticker_picker.dart"; +import "package:island/wallet/wallet_models/wallet.dart"; +import "package:island/realms/realms_models/realm.dart"; +import "package:island/stickers/stickers_models/sticker.dart"; +import "package:island/core/config.dart"; +import "package:island/accounts/accounts_pod.dart"; +import "package:island/discovery/discovery_service.dart"; +import "package:island/core/services/responsive.dart"; +import "package:island/core/widgets/content/attachment_preview.dart"; +import "package:island/drive/drive_widgets/cloud_files.dart"; +import "package:island/core/widgets/shared/upload_menu.dart"; import "package:material_symbols_icons/material_symbols_icons.dart"; import "package:pasteboard/pasteboard.dart"; import "package:styled_widget/styled_widget.dart"; import "package:material_symbols_icons/symbols.dart"; -import "package:island/widgets/stickers/sticker_picker.dart"; -import "package:island/pods/chat/chat_subscribe.dart"; -import "package:island/widgets/post/compose_poll.dart"; -import "package:island/widgets/post/compose_fund.dart"; +import "package:island/chat/chat_pod/chat_subscribe.dart"; void _insertPlaceholder(TextEditingController controller, String placeholder) { final text = controller.text; diff --git a/lib/chat/chat_widgets/chat_invites_sheet.dart b/lib/chat/chat_widgets/chat_invites_sheet.dart new file mode 100644 index 00000000..8cb4b716 --- /dev/null +++ b/lib/chat/chat_widgets/chat_invites_sheet.dart @@ -0,0 +1,103 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/chat/chat_pod/chat_room.dart'; +import 'package:island/chat/chat_widgets/chat_room_list_tile.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/realms/realm/realms.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:material_symbols_icons/symbols.dart'; + +class ChatInvitesSheet extends HookConsumerWidget { + const ChatInvitesSheet({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final invites = ref.watch(chatroomInvitesProvider); + + Future acceptInvite(SnChatMember invite) async { + try { + final client = ref.read(apiClientProvider); + await client.post( + '/messager/chat/invites/${invite.chatRoom!.id}/accept', + ); + ref.invalidate(chatroomInvitesProvider); + ref.invalidate(chatRoomJoinedProvider); + } catch (err) { + showErrorAlert(err); + } + } + + Future declineInvite(SnChatMember invite) async { + try { + final client = ref.read(apiClientProvider); + await client.post( + '/messager/chat/invites/${invite.chatRoom!.id}/decline', + ); + ref.invalidate(chatroomInvitesProvider); + } catch (err) { + showErrorAlert(err); + } + } + + return SheetScaffold( + titleText: 'invites'.tr(), + actions: [ + IconButton( + icon: const Icon(Symbols.refresh), + style: IconButton.styleFrom(minimumSize: const Size(36, 36)), + onPressed: () { + ref.invalidate(realmInvitesProvider); + }, + ), + ], + child: invites.when( + data: (items) => items.isEmpty + ? Center( + child: Text('invitesEmpty', textAlign: TextAlign.center).tr(), + ) + : ListView.builder( + shrinkWrap: true, + itemCount: items.length, + itemBuilder: (context, index) { + final invite = items[index]; + return ChatRoomListTile( + room: invite.chatRoom!, + isDirect: invite.chatRoom!.type == 1, + subtitle: Row( + spacing: 6, + children: [ + if (invite.chatRoom!.type == 1) + Badge( + label: const Text('directMessage').tr(), + backgroundColor: Theme.of( + context, + ).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + ), + ], + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Symbols.check), + onPressed: () => acceptInvite(invite), + ), + IconButton( + icon: const Icon(Symbols.close), + onPressed: () => declineInvite(invite), + ), + ], + ), + ); + }, + ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => Center(child: Text('Error: $error')), + ), + ); + } +} diff --git a/lib/widgets/chat/chat_link_attachments.dart b/lib/chat/chat_widgets/chat_link_attachments.dart similarity index 95% rename from lib/widgets/chat/chat_link_attachments.dart rename to lib/chat/chat_widgets/chat_link_attachments.dart index 071bb908..0aa1a7f8 100644 --- a/lib/widgets/chat/chat_link_attachments.dart +++ b/lib/chat/chat_widgets/chat_link_attachments.dart @@ -3,12 +3,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/core/network.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher_string.dart'; diff --git a/lib/screens/chat/chat.dart b/lib/chat/chat_widgets/chat_list_screen.dart similarity index 79% rename from lib/screens/chat/chat.dart rename to lib/chat/chat_widgets/chat_list_screen.dart index 5daab878..a5d9f338 100644 --- a/lib/screens/chat/chat.dart +++ b/lib/chat/chat_widgets/chat_list_screen.dart @@ -1,30 +1,28 @@ -import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/pods/chat/chat_summary.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/screens/chat/chat_form.dart'; -import 'package:island/screens/realm/realms.dart'; -import 'package:island/services/event_bus.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/account/account_picker.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/chat_room_widgets.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/accounts/accounts_widgets/account/account_picker.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/chat/chat_pod/chat_room.dart'; +import 'package:island/chat/chat_pod/chat_summary.dart'; +import 'package:island/chat/chat_widgets/chat_invites_sheet.dart'; +import 'package:island/chat/chat_widgets/chat_room_form.dart'; +import 'package:island/chat/chat_widgets/chat_room_list_tile.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/event_bus.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:island/pods/chat/chat_room.dart'; -import 'package:island/pods/config.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; class ChatListBodyWidget extends HookConsumerWidget { @@ -500,7 +498,7 @@ class ChatListScreen extends HookConsumerWidget { useRootNavigator: true, isScrollControlled: true, context: context, - builder: (context) => const _ChatInvitesSheet(), + builder: (context) => const ChatInvitesSheet(), ); }, ), @@ -604,7 +602,7 @@ class ChatListScreen extends HookConsumerWidget { useRootNavigator: true, isScrollControlled: true, context: context, - builder: (context) => const _ChatInvitesSheet(), + builder: (context) => const ChatInvitesSheet(), ); }, ), @@ -623,167 +621,3 @@ class ChatListScreen extends HookConsumerWidget { ); } } - -class _ChatInvitesSheet extends HookConsumerWidget { - const _ChatInvitesSheet(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final invites = ref.watch(chatroomInvitesProvider); - - Future acceptInvite(SnChatMember invite) async { - try { - final client = ref.read(apiClientProvider); - await client.post( - '/messager/chat/invites/${invite.chatRoom!.id}/accept', - ); - ref.invalidate(chatroomInvitesProvider); - ref.invalidate(chatRoomJoinedProvider); - } catch (err) { - showErrorAlert(err); - } - } - - Future declineInvite(SnChatMember invite) async { - try { - final client = ref.read(apiClientProvider); - await client.post( - '/messager/chat/invites/${invite.chatRoom!.id}/decline', - ); - ref.invalidate(chatroomInvitesProvider); - } catch (err) { - showErrorAlert(err); - } - } - - return SheetScaffold( - titleText: 'invites'.tr(), - actions: [ - IconButton( - icon: const Icon(Symbols.refresh), - style: IconButton.styleFrom(minimumSize: const Size(36, 36)), - onPressed: () { - ref.invalidate(realmInvitesProvider); - }, - ), - ], - child: invites.when( - data: (items) => items.isEmpty - ? Center( - child: Text('invitesEmpty', textAlign: TextAlign.center).tr(), - ) - : ListView.builder( - shrinkWrap: true, - itemCount: items.length, - itemBuilder: (context, index) { - final invite = items[index]; - return ChatRoomListTile( - room: invite.chatRoom!, - isDirect: invite.chatRoom!.type == 1, - subtitle: Row( - spacing: 6, - children: [ - if (invite.chatRoom!.type == 1) - Badge( - label: const Text('directMessage').tr(), - backgroundColor: Theme.of( - context, - ).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - ), - ], - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon(Symbols.check), - onPressed: () => acceptInvite(invite), - ), - IconButton( - icon: const Icon(Symbols.close), - onPressed: () => declineInvite(invite), - ), - ], - ), - ); - }, - ), - loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stack) => Center(child: Text('Error: $error')), - ), - ); - } -} - -class ChatRoomListTile extends HookConsumerWidget { - final SnChatRoom room; - final bool isDirect; - final Widget? subtitle; - final Widget? trailing; - final VoidCallback? onTap; - - const ChatRoomListTile({ - super.key, - required this.room, - this.isDirect = false, - this.subtitle, - this.trailing, - this.onTap, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final summary = ref - .watch(chatSummaryProvider) - .whenData((summaries) => summaries[room.id]); - - var validMembers = room.members ?? []; - if (validMembers.isNotEmpty) { - final userInfo = ref.watch(userInfoProvider); - if (userInfo.value != null) { - validMembers = validMembers - .where((e) => e.accountId != userInfo.value!.id) - .toList(); - } - } - - String titleText; - if (isDirect && room.name == null) { - if (room.members?.isNotEmpty ?? false) { - titleText = validMembers.map((e) => e.account.nick).join(', '); - } else { - titleText = 'Direct Message'; - } - } else { - titleText = room.name ?? ''; - } - - return ListTile( - leading: ChatRoomAvatar( - room: room, - isDirect: isDirect, - summary: summary, - validMembers: validMembers, - ), - title: Text(titleText), - subtitle: ChatRoomSubtitle( - room: room, - isDirect: isDirect, - validMembers: validMembers, - summary: summary, - subtitle: subtitle, - ), - trailing: trailing, // Add this line - onTap: () async { - // Clear unread count if there are unread messages - ref.read(chatSummaryProvider.future).then((summary) { - if ((summary[room.id]?.unreadCount ?? 0) > 0) { - ref.read(chatSummaryProvider.notifier).clearUnreadCount(room.id); - } - }); - onTap?.call(); - }, - ); - } -} diff --git a/lib/screens/chat/chat_form.dart b/lib/chat/chat_widgets/chat_room_form.dart similarity index 94% rename from lib/screens/chat/chat_form.dart rename to lib/chat/chat_widgets/chat_room_form.dart index 690f7b08..053eec51 100644 --- a/lib/screens/chat/chat_form.dart +++ b/lib/chat/chat_widgets/chat_room_form.dart @@ -3,23 +3,23 @@ import 'package:croppy/croppy.dart' hide cropImage; import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/pods/chat/chat_room.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/realm/realms.dart'; -import 'package:island/services/file.dart'; -import 'package:island/services/file_uploader.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/chat/chat_pod/chat_room.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/image.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/realms/realm/realms.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/shared/widgets/alert.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:island/widgets/content/sheet.dart'; class NewChatScreen extends StatelessWidget { const NewChatScreen({super.key}); diff --git a/lib/chat/chat_widgets/chat_room_list_tile.dart b/lib/chat/chat_widgets/chat_room_list_tile.dart new file mode 100644 index 00000000..0616e0d0 --- /dev/null +++ b/lib/chat/chat_widgets/chat_room_list_tile.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/chat/chat_pod/chat_summary.dart'; +import 'package:island/chat/chat_widgets/chat_room_widgets.dart'; + +class ChatRoomListTile extends HookConsumerWidget { + final SnChatRoom room; + final bool isDirect; + final Widget? subtitle; + final Widget? trailing; + final VoidCallback? onTap; + + const ChatRoomListTile({ + super.key, + required this.room, + this.isDirect = false, + this.subtitle, + this.trailing, + this.onTap, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final summary = ref + .watch(chatSummaryProvider) + .whenData((summaries) => summaries[room.id]); + + var validMembers = room.members ?? []; + if (validMembers.isNotEmpty) { + final userInfo = ref.watch(userInfoProvider); + if (userInfo.value != null) { + validMembers = validMembers + .where((e) => e.accountId != userInfo.value!.id) + .toList(); + } + } + + String titleText; + if (isDirect && room.name == null) { + if (room.members?.isNotEmpty ?? false) { + titleText = validMembers.map((e) => e.account.nick).join(', '); + } else { + titleText = 'Direct Message'; + } + } else { + titleText = room.name ?? ''; + } + + return ListTile( + leading: ChatRoomAvatar( + room: room, + isDirect: isDirect, + summary: summary, + validMembers: validMembers, + ), + title: Text(titleText), + subtitle: ChatRoomSubtitle( + room: room, + isDirect: isDirect, + validMembers: validMembers, + summary: summary, + subtitle: subtitle, + ), + trailing: trailing, // Add this line + onTap: () async { + // Clear unread count if there are unread messages + ref.read(chatSummaryProvider.future).then((summary) { + if ((summary[room.id]?.unreadCount ?? 0) > 0) { + ref.read(chatSummaryProvider.notifier).clearUnreadCount(room.id); + } + }); + onTap?.call(); + }, + ); + } +} diff --git a/lib/screens/chat/room.dart b/lib/chat/chat_widgets/chat_room_screen.dart similarity index 90% rename from lib/screens/chat/room.dart rename to lib/chat/chat_widgets/chat_room_screen.dart index e3b423fc..75b6e78f 100644 --- a/lib/screens/chat/room.dart +++ b/lib/chat/chat_widgets/chat_room_screen.dart @@ -1,35 +1,36 @@ -import "dart:async"; -import "package:easy_localization/easy_localization.dart"; -import "package:flutter/material.dart"; -import "package:go_router/go_router.dart"; -import "package:flutter_hooks/flutter_hooks.dart"; -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:island/pods/chat/chat_room.dart"; -import "package:island/pods/chat/messages_notifier.dart"; -import "package:island/pods/network.dart"; -import "package:island/pods/chat/chat_online_count.dart"; -import "package:island/pods/config.dart"; -import "package:island/screens/chat/search_messages.dart"; -import "package:island/screens/chat/public_room_preview.dart"; -import "package:island/services/analytics_service.dart"; -import "package:island/services/responsive.dart"; -import "package:island/widgets/alert.dart"; -import "package:island/widgets/app_scaffold.dart"; -import "package:island/widgets/response.dart"; -import "package:island/widgets/attachment_uploader.dart"; -import "package:island/widgets/chat/call_button.dart"; -import "package:island/widgets/chat/chat_input.dart"; -import "package:island/widgets/chat/room_app_bar.dart"; -import "package:island/widgets/chat/room_message_list.dart"; -import "package:island/widgets/chat/room_selection_mode.dart"; -import "package:island/widgets/chat/room_overlays.dart"; -import "package:island/services/file_uploader.dart"; -import "package:island/models/file.dart"; -import "package:island/screens/thought/think_sheet.dart"; -import "package:styled_widget/styled_widget.dart"; -import "package:island/hooks/use_room_scroll.dart"; -import "package:island/hooks/use_room_file_picker.dart"; -import "package:island/hooks/use_room_input.dart"; +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/chat/chat_pod/chat_online_count.dart'; +import 'package:island/chat/chat_pod/chat_room.dart'; +import 'package:island/chat/chat_widgets/call_button.dart'; +import 'package:island/chat/chat_widgets/chat_input.dart'; +import 'package:island/chat/chat_widgets/chat_search_screen.dart'; +import 'package:island/chat/chat_widgets/public_room_preview.dart'; +import 'package:island/chat/chat_widgets/room_app_bar.dart'; +import 'package:island/chat/chat_widgets/room_message_list.dart'; +import 'package:island/chat/chat_widgets/room_overlays.dart'; +import 'package:island/chat/chat_widgets/room_selection_mode.dart'; +import 'package:island/chat/hooks/use_room_file_picker.dart'; +import 'package:island/chat/hooks/use_room_input.dart'; +import 'package:island/chat/hooks/use_room_scroll.dart'; +import 'package:island/chat/messages_notifier.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/analytics_service.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/attachment_uploader.dart'; +import 'package:island/shared/widgets/response.dart'; +import 'package:island/thought/thought/think_sheet.dart'; +import 'package:styled_widget/styled_widget.dart'; class ChatRoomScreen extends HookConsumerWidget { final String id; diff --git a/lib/widgets/chat_room_widgets.dart b/lib/chat/chat_widgets/chat_room_widgets.dart similarity index 98% rename from lib/widgets/chat_room_widgets.dart rename to lib/chat/chat_widgets/chat_room_widgets.dart index af2264a2..7b0f779a 100644 --- a/lib/widgets/chat_room_widgets.dart +++ b/lib/chat/chat_widgets/chat_room_widgets.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:relative_time/relative_time.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:easy_localization/easy_localization.dart'; diff --git a/lib/screens/chat/search_messages.dart b/lib/chat/chat_widgets/chat_search_screen.dart similarity index 65% rename from lib/screens/chat/search_messages.dart rename to lib/chat/chat_widgets/chat_search_screen.dart index e9b958c1..3b853f9f 100644 --- a/lib/screens/chat/search_messages.dart +++ b/lib/chat/chat_widgets/chat_search_screen.dart @@ -1,18 +1,18 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/chat/chat_room.dart'; -import 'package:island/pods/chat/messages_notifier.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/chat/message_list_tile.dart'; -import 'package:material_symbols_icons/material_symbols_icons.dart'; +import 'package:island/chat/chat_pod/chat_room.dart'; +import 'package:island/chat/chat_widgets/message_list_tile.dart'; +import 'package:island/chat/messages_notifier.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; -import 'package:island/services/responsive.dart'; -import 'dart:async'; -// Class to represent the result when popping from search messages class SearchMessagesResult { final String messageId; const SearchMessagesResult(this.messageId); @@ -44,10 +44,9 @@ class _SearchFilters extends StatelessWidget { IconButton( icon: Icon( Symbols.link, - color: - withLinks.value - ? Theme.of(context).colorScheme.primary - : Theme.of(context).iconTheme.color, + color: withLinks.value + ? Theme.of(context).colorScheme.primary + : Theme.of(context).iconTheme.color, ), onPressed: () { withLinks.value = !withLinks.value; @@ -58,10 +57,9 @@ class _SearchFilters extends StatelessWidget { IconButton( icon: Icon( Symbols.file_copy, - color: - withAttachments.value - ? Theme.of(context).colorScheme.primary - : Theme.of(context).iconTheme.color, + color: withAttachments.value + ? Theme.of(context).colorScheme.primary + : Theme.of(context).iconTheme.color, ), onPressed: () { withAttachments.value = !withAttachments.value; @@ -153,8 +151,9 @@ class SearchMessagesScreen extends HookConsumerWidget { withAttachments: withAttachments.value, ); searchResults.value = AsyncValue.data(results); - searchState.value = - results.isEmpty ? SearchState.noResults : SearchState.results; + searchState.value = results.isEmpty + ? SearchState.noResults + : SearchState.results; searchResultCount.value = results.length; } catch (error, stackTrace) { searchResults.value = AsyncValue.error(error, stackTrace); @@ -188,13 +187,12 @@ class SearchMessagesScreen extends HookConsumerWidget { return AppScaffold( appBar: AppBar( title: const Text('searchMessages').tr(), - bottom: - searchState.value == SearchState.searching - ? const PreferredSize( - preferredSize: Size.fromHeight(2), - child: LinearProgressIndicator(), - ) - : null, + bottom: searchState.value == SearchState.searching + ? const PreferredSize( + preferredSize: Size.fromHeight(2), + child: LinearProgressIndicator(), + ) + : null, ), body: Column( children: [ @@ -207,89 +205,11 @@ class SearchMessagesScreen extends HookConsumerWidget { borderRadius: BorderRadius.circular(32), child: Padding( padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8), - child: - isLarge - ? Row( - children: [ - Expanded( - child: TextField( - controller: searchController, - autofocus: true, - decoration: InputDecoration( - hintText: 'searchMessagesHint'.tr(), - border: InputBorder.none, - isDense: true, - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 12, - ), - suffixIcon: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (searchResultCount.value != null && - searchState.value == - SearchState.results) - Container( - margin: const EdgeInsets.only( - right: 8, - ), - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 2, - ), - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .primary - .withOpacity(0.1), - borderRadius: BorderRadius.circular( - 12, - ), - ), - child: Text( - '${searchResultCount.value}', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: - Theme.of( - context, - ).colorScheme.primary, - ), - ), - ), - if (searchController.text.isNotEmpty) - IconButton( - iconSize: 18, - visualDensity: VisualDensity.compact, - icon: const Icon(Icons.clear), - onPressed: () { - searchController.clear(); - performSearch(''); - }, - ), - ], - ), - ), - onChanged: performSearch, - ), - ), - const SizedBox(width: 16), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: _SearchFilters( - withLinks: withLinks, - withAttachments: withAttachments, - performSearch: performSearch, - searchController: searchController, - isLarge: isLarge, - ), - ), - ], - ) - : Column( - children: [ - TextField( + child: isLarge + ? Row( + children: [ + Expanded( + child: TextField( controller: searchController, autofocus: true, decoration: InputDecoration( @@ -326,10 +246,9 @@ class SearchMessagesScreen extends HookConsumerWidget { style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, - color: - Theme.of( - context, - ).colorScheme.primary, + color: Theme.of( + context, + ).colorScheme.primary, ), ), ), @@ -348,28 +267,98 @@ class SearchMessagesScreen extends HookConsumerWidget { ), onChanged: performSearch, ), - Padding( - padding: const EdgeInsets.only( - left: 8, - right: 8, - top: 8, - bottom: 8, + ), + const SizedBox(width: 16), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: _SearchFilters( + withLinks: withLinks, + withAttachments: withAttachments, + performSearch: performSearch, + searchController: searchController, + isLarge: isLarge, + ), + ), + ], + ) + : Column( + children: [ + TextField( + controller: searchController, + autofocus: true, + decoration: InputDecoration( + hintText: 'searchMessagesHint'.tr(), + border: InputBorder.none, + isDense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 12, ), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, + suffixIcon: Row( + mainAxisSize: MainAxisSize.min, children: [ - _SearchFilters( - withLinks: withLinks, - withAttachments: withAttachments, - performSearch: performSearch, - searchController: searchController, - isLarge: false, - ), + if (searchResultCount.value != null && + searchState.value == SearchState.results) + Container( + margin: const EdgeInsets.only(right: 8), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: Theme.of( + context, + ).colorScheme.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '${searchResultCount.value}', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Theme.of( + context, + ).colorScheme.primary, + ), + ), + ), + if (searchController.text.isNotEmpty) + IconButton( + iconSize: 18, + visualDensity: VisualDensity.compact, + icon: const Icon(Icons.clear), + onPressed: () { + searchController.clear(); + performSearch(''); + }, + ), ], ), ), - ], - ), + onChanged: performSearch, + ), + Padding( + padding: const EdgeInsets.only( + left: 8, + right: 8, + top: 8, + bottom: 8, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + _SearchFilters( + withLinks: withLinks, + withAttachments: withAttachments, + performSearch: performSearch, + searchController: searchController, + isLarge: false, + ), + ], + ), + ), + ], + ), ), ), ), @@ -392,11 +381,10 @@ class SearchMessagesScreen extends HookConsumerWidget { const SizedBox(height: 16), Text( 'searchMessagesHint'.tr(), - style: Theme.of( - context, - ).textTheme.bodyLarge?.copyWith( - color: Theme.of(context).disabledColor, - ), + style: Theme.of(context).textTheme.bodyLarge + ?.copyWith( + color: Theme.of(context).disabledColor, + ), ), ], ), @@ -415,20 +403,18 @@ class SearchMessagesScreen extends HookConsumerWidget { const SizedBox(height: 16), Text( 'noMessagesFound'.tr(), - style: Theme.of( - context, - ).textTheme.bodyLarge?.copyWith( - color: Theme.of(context).disabledColor, - ), + style: Theme.of(context).textTheme.bodyLarge + ?.copyWith( + color: Theme.of(context).disabledColor, + ), ), const SizedBox(height: 8), Text( 'tryDifferentKeywords'.tr(), - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith( - color: Theme.of(context).disabledColor, - ), + style: Theme.of(context).textTheme.bodySmall + ?.copyWith( + color: Theme.of(context).disabledColor, + ), ), ], ), diff --git a/lib/widgets/chat/message_content.dart b/lib/chat/chat_widgets/message_content.dart similarity index 97% rename from lib/widgets/chat/message_content.dart rename to lib/chat/chat_widgets/message_content.dart index b2a15a3f..b8d4059e 100644 --- a/lib/widgets/chat/message_content.dart +++ b/lib/chat/chat_widgets/message_content.dart @@ -3,9 +3,9 @@ import 'dart:math' as math; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/pods/chat/call.dart'; -import 'package:island/widgets/content/markdown.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/chat/chat_pod/call.dart'; +import 'package:island/core/widgets/content/markdown.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:pretty_diff_text/pretty_diff_text.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/chat/message_indicators.dart b/lib/chat/chat_widgets/message_indicators.dart similarity index 98% rename from lib/widgets/chat/message_indicators.dart rename to lib/chat/chat_widgets/message_indicators.dart index ef4bd255..4a2acc4d 100644 --- a/lib/widgets/chat/message_indicators.dart +++ b/lib/chat/chat_widgets/message_indicators.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/database/message.dart'; +import 'package:island/data/message.dart'; import 'package:styled_widget/styled_widget.dart'; class MessageIndicators extends StatelessWidget { diff --git a/lib/widgets/chat/message_item.dart b/lib/chat/chat_widgets/message_item.dart similarity index 85% rename from lib/widgets/chat/message_item.dart rename to lib/chat/chat_widgets/message_item.dart index 7cd0ce77..1e336f1b 100644 --- a/lib/widgets/chat/message_item.dart +++ b/lib/chat/chat_widgets/message_item.dart @@ -8,25 +8,25 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/database/message.dart'; -import 'package:island/pods/chat/chat_room.dart'; -import 'package:island/pods/chat/messages_notifier.dart'; -import 'package:island/pods/translate.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/account/account_pfc.dart'; -import 'package:island/widgets/chat/message_content.dart'; -import 'package:island/widgets/chat/message_indicators.dart'; -import 'package:island/widgets/chat/message_sender_info.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_file_collection.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/embed/embed_list.dart'; -import 'package:island/widgets/post/post_shared.dart'; +import 'package:island/accounts/accounts_widgets/account/account_pfc.dart'; +import 'package:island/chat/chat_widgets/message_content.dart'; +import 'package:island/chat/chat_widgets/message_indicators.dart'; +import 'package:island/chat/chat_widgets/message_sender_info.dart'; +import 'package:island/chat/messages_notifier.dart'; +import 'package:island/data/message.dart'; +import 'package:island/chat/chat_pod/chat_room.dart'; +import 'package:island/core/translate.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/cloud_file_collection.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/embed/embed_list.dart'; +import 'package:island/posts/posts_widgets/post/post_shared.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:swipe_to/swipe_to.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/core/widgets/content/sheet.dart'; class MessageItemAction { static const String edit = "edit"; @@ -107,20 +107,19 @@ class MessageItem extends HookConsumerWidget { if (onAction == null) return; showModalBottomSheet( context: context, - builder: - (context) => MessageActionSheet( - isCurrentUser: isCurrentUser, - onAction: onAction, - translatableLanguage: translatableLanguage, - translating: translating.value, - translatedText: translatedText.value, - translate: translate, - isMobile: isMobile, - remoteMessage: remoteMessage, - message: message, - onToggleSelection: onToggleSelection, - onEnterSelectionMode: onEnterSelectionMode, - ), + builder: (context) => MessageActionSheet( + isCurrentUser: isCurrentUser, + onAction: onAction, + translatableLanguage: translatableLanguage, + translating: translating.value, + translatedText: translatedText.value, + translate: translate, + isMobile: isMobile, + remoteMessage: remoteMessage, + message: message, + onToggleSelection: onToggleSelection, + onEnterSelectionMode: onEnterSelectionMode, + ), ); } @@ -160,10 +159,9 @@ class MessageItem extends HookConsumerWidget { }; }, [flashing]); - final flashColor = - isFlashing.value - ? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.8) - : Colors.transparent; + final flashColor = isFlashing.value + ? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.8) + : Colors.transparent; final isHovered = useState(false); @@ -627,8 +625,9 @@ class MessageHoverActionMenu extends StatelessWidget { IconButton( icon: Icon(Symbols.translate, size: 16), onPressed: translate, - tooltip: - translatedText == null ? 'translate'.tr() : 'translated'.tr(), + tooltip: translatedText == null + ? 'translate'.tr() + : 'translated'.tr(), padding: const EdgeInsets.all(8), constraints: const BoxConstraints(minWidth: 32, minHeight: 32), ), @@ -709,14 +708,12 @@ class MessageItemDisplayBubble extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final textColor = - isCurrentUser - ? Theme.of(context).colorScheme.onPrimaryContainer - : Theme.of(context).colorScheme.onSurfaceVariant; - final containerColor = - isCurrentUser - ? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.5) - : Theme.of(context).colorScheme.surfaceContainer; + final textColor = isCurrentUser + ? Theme.of(context).colorScheme.onPrimaryContainer + : Theme.of(context).colorScheme.onSurfaceVariant; + final containerColor = isCurrentUser + ? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.5) + : Theme.of(context).colorScheme.surfaceContainer; final remoteMessage = message.toRemoteMessage(); final sender = remoteMessage.sender; @@ -853,14 +850,15 @@ class MessageItemDisplayIRC extends HookConsumerWidget { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), child: Row( - crossAxisAlignment: - isMultiline ? CrossAxisAlignment.start : CrossAxisAlignment.center, + crossAxisAlignment: isMultiline + ? CrossAxisAlignment.start + : CrossAxisAlignment.center, children: [ Text( DateFormat('HH:mm').format(message.createdAt.toLocal()), style: TextStyle(color: textColor.withOpacity(0.7), fontSize: 12), ).padding(top: isMultiline ? 2 : 0), - AccountPfcGestureDetector( + AccountPfcRegion( uname: sender.account.name, child: Row( crossAxisAlignment: CrossAxisAlignment.center, @@ -977,98 +975,30 @@ class MessageItemDisplayDiscord extends HookConsumerWidget { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), - child: - showAvatar - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - spacing: 8, - children: [ - AccountPfcGestureDetector( - uname: sender.account.name, - child: ProfilePictureWidget( - file: sender.account.profile.picture, - radius: kAvatarRadius, - ), + child: showAvatar + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8, + children: [ + AccountPfcRegion( + uname: sender.account.name, + child: ProfilePictureWidget( + file: sender.account.profile.picture, + radius: kAvatarRadius, ), - MessageSenderInfo( - sender: sender, - createdAt: message.createdAt, - textColor: textColor, - showAvatar: false, - isCompact: true, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (remoteMessage.repliedMessageId != null) - MessageQuoteWidget( - message: message, - textColor: textColor, - isReply: true, - ).padding(vertical: 4), - if (remoteMessage.forwardedMessageId != null) - MessageQuoteWidget( - message: message, - textColor: textColor, - isReply: false, - ).padding(vertical: 4), - if (MessageContent.hasContent(remoteMessage)) - MessageContent( - item: remoteMessage, - translatedText: translatedText, - ), - if (remoteMessage.attachments.isNotEmpty) - LayoutBuilder( - builder: (context, constraints) { - return CloudFileList( - files: remoteMessage.attachments, - maxWidth: constraints.maxWidth, - padding: EdgeInsets.symmetric(vertical: 4), - ); - }, - ), - if (remoteMessage.meta['embeds'] != null && - kMessageEnableEmbedTypes.contains(message.type)) - EmbedListWidget( - embeds: - remoteMessage.meta['embeds'] - as List, - isInteractive: true, - isFullPost: false, - renderingPadding: EdgeInsets.zero, - maxWidth: 480, - ), - FileUploadProgressWidget( - progress: progress, - textColor: textColor, - hasContent: - remoteMessage.content?.isNotEmpty ?? false, - ), - ], - ), - ), - MessageIndicators( - editedAt: remoteMessage.editedAt, - status: message.status, - isCurrentUser: isCurrentUser, - textColor: textColor, - ), - ], - ).padding(left: kAvatarRadius * 2 + 8), - ], - ) - : Padding( - padding: EdgeInsets.only(left: kAvatarRadius * 2 + 8), - child: Row( + ), + MessageSenderInfo( + sender: sender, + createdAt: message.createdAt, + textColor: textColor, + showAvatar: false, + isCompact: true, + ), + ], + ), + Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ @@ -1129,8 +1059,74 @@ class MessageItemDisplayDiscord extends HookConsumerWidget { textColor: textColor, ), ], - ), + ).padding(left: kAvatarRadius * 2 + 8), + ], + ) + : Padding( + padding: EdgeInsets.only(left: kAvatarRadius * 2 + 8), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (remoteMessage.repliedMessageId != null) + MessageQuoteWidget( + message: message, + textColor: textColor, + isReply: true, + ).padding(vertical: 4), + if (remoteMessage.forwardedMessageId != null) + MessageQuoteWidget( + message: message, + textColor: textColor, + isReply: false, + ).padding(vertical: 4), + if (MessageContent.hasContent(remoteMessage)) + MessageContent( + item: remoteMessage, + translatedText: translatedText, + ), + if (remoteMessage.attachments.isNotEmpty) + LayoutBuilder( + builder: (context, constraints) { + return CloudFileList( + files: remoteMessage.attachments, + maxWidth: constraints.maxWidth, + padding: EdgeInsets.symmetric(vertical: 4), + ); + }, + ), + if (remoteMessage.meta['embeds'] != null && + kMessageEnableEmbedTypes.contains(message.type)) + EmbedListWidget( + embeds: + remoteMessage.meta['embeds'] as List, + isInteractive: true, + isFullPost: false, + renderingPadding: EdgeInsets.zero, + maxWidth: 480, + ), + FileUploadProgressWidget( + progress: progress, + textColor: textColor, + hasContent: + remoteMessage.content?.isNotEmpty ?? false, + ), + ], + ), + ), + MessageIndicators( + editedAt: remoteMessage.editedAt, + status: message.status, + isCurrentUser: isCurrentUser, + textColor: textColor, + ), + ], ), + ), ); } } @@ -1160,21 +1156,21 @@ class MessageQuoteWidget extends HookConsumerWidget { : message.toRemoteMessage().forwardedMessageId!, ), builder: (context, snapshot) { - final remoteMessage = - snapshot.hasData ? snapshot.data!.toRemoteMessage() : null; + final remoteMessage = snapshot.hasData + ? snapshot.data!.toRemoteMessage() + : null; if (remoteMessage != null) { return ClipRRect( borderRadius: BorderRadius.all(Radius.circular(8)), child: GestureDetector( onTap: () { - final messageId = - isReply - ? message.toRemoteMessage().repliedMessageId! - : message.toRemoteMessage().forwardedMessageId!; + final messageId = isReply + ? message.toRemoteMessage().repliedMessageId! + : message.toRemoteMessage().forwardedMessageId!; // Find the nearest MessageItem ancestor and call its onJump method - final MessageItem? ancestor = - context.findAncestorWidgetOfExactType(); + final MessageItem? ancestor = context + .findAncestorWidgetOfExactType(); if (ancestor != null) { ancestor.onJump(messageId); } diff --git a/lib/screens/chat/widgets/message_item_wrapper.dart b/lib/chat/chat_widgets/message_item_wrapper.dart similarity index 96% rename from lib/screens/chat/widgets/message_item_wrapper.dart rename to lib/chat/chat_widgets/message_item_wrapper.dart index 30e279c9..ea86736f 100644 --- a/lib/screens/chat/widgets/message_item_wrapper.dart +++ b/lib/chat/chat_widgets/message_item_wrapper.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/chat/chat_widgets/message_item.dart'; +import 'package:island/data/message.dart'; -import 'package:island/database/message.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/widgets/chat/message_item.dart'; - -// Provider to track animated messages to prevent replay final animatedMessagesProvider = NotifierProvider.autoDispose( AnimatedMessagesNotifier.new, ); diff --git a/lib/widgets/chat/message_list_tile.dart b/lib/chat/chat_widgets/message_list_tile.dart similarity index 84% rename from lib/widgets/chat/message_list_tile.dart rename to lib/chat/chat_widgets/message_list_tile.dart index a59ab7df..4393af65 100644 --- a/lib/widgets/chat/message_list_tile.dart +++ b/lib/chat/chat_widgets/message_list_tile.dart @@ -1,14 +1,14 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; -import 'package:island/database/message.dart'; -import 'package:island/models/embed.dart'; -import 'package:island/utils/mapping.dart'; -import 'package:island/widgets/chat/message_content.dart'; -import 'package:island/widgets/chat/message_sender_info.dart'; -import 'package:island/widgets/content/cloud_file_collection.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/embed/link.dart'; +import 'package:island/chat/chat_widgets/message_content.dart'; +import 'package:island/chat/chat_widgets/message_sender_info.dart'; +import 'package:island/data/message.dart'; +import 'package:island/posts/posts_models/embed.dart'; +import 'package:island/core/utils/mapping.dart'; +import 'package:island/core/widgets/content/cloud_file_collection.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/embed/link.dart'; class MessageListTile extends StatelessWidget { final LocalChatMessage message; diff --git a/lib/widgets/chat/message_sender_info.dart b/lib/chat/chat_widgets/message_sender_info.dart similarity index 90% rename from lib/widgets/chat/message_sender_info.dart rename to lib/chat/chat_widgets/message_sender_info.dart index f6cf0f60..89fbce99 100644 --- a/lib/widgets/chat/message_sender_info.dart +++ b/lib/chat/chat_widgets/message_sender_info.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:gap/gap.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/widgets/account/account_name.dart'; -import 'package:island/widgets/account/account_pfc.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/accounts/accounts_widgets/account/account_name.dart'; +import 'package:island/accounts/accounts_widgets/account/account_pfc.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; class MessageSenderInfo extends StatelessWidget { final SnChatMember sender; @@ -37,7 +37,7 @@ class MessageSenderInfo extends StatelessWidget { textBaseline: TextBaseline.alphabetic, children: [ if (showAvatar) - AccountPfcGestureDetector( + AccountPfcRegion( uname: sender.account.name, child: ProfilePictureWidget( file: sender.account.profile.picture, @@ -65,7 +65,7 @@ class MessageSenderInfo extends StatelessWidget { return Row( spacing: 8, children: [ - AccountPfcGestureDetector( + AccountPfcRegion( uname: sender.account.name, child: ProfilePictureWidget( file: sender.account.profile.picture, @@ -102,7 +102,7 @@ class MessageSenderInfo extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ if (showAvatar) - AccountPfcGestureDetector( + AccountPfcRegion( uname: sender.account.name, child: ProfilePictureWidget( file: sender.account.profile.picture, diff --git a/lib/widgets/chat/public_room_preview.dart b/lib/chat/chat_widgets/public_room_preview.dart similarity index 92% rename from lib/widgets/chat/public_room_preview.dart rename to lib/chat/chat_widgets/public_room_preview.dart index bbc36f68..73c13557 100644 --- a/lib/widgets/chat/public_room_preview.dart +++ b/lib/chat/chat_widgets/public_room_preview.dart @@ -4,17 +4,17 @@ import "package:go_router/go_router.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:gap/gap.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:island/database/message.dart"; -import "package:island/models/chat.dart"; -import "package:island/pods/chat/chat_room.dart"; -import "package:island/pods/chat/messages_notifier.dart"; -import "package:island/pods/network.dart"; -import "package:island/services/responsive.dart"; -import "package:island/widgets/alert.dart"; -import "package:island/widgets/app_scaffold.dart"; -import "package:island/widgets/chat/message_item.dart"; -import "package:island/widgets/content/cloud_files.dart"; -import "package:island/widgets/response.dart"; +import "package:island/chat/chat_widgets/message_item.dart"; +import "package:island/chat/messages_notifier.dart"; +import "package:island/data/message.dart"; +import "package:island/chat/chat_models/chat.dart"; +import "package:island/chat/chat_pod/chat_room.dart"; +import "package:island/core/network.dart"; +import "package:island/core/services/responsive.dart"; +import "package:island/shared/widgets/alert.dart"; +import "package:island/shared/widgets/app_scaffold.dart"; +import "package:island/drive/drive_widgets/cloud_files.dart"; +import "package:island/shared/widgets/response.dart"; import "package:material_symbols_icons/material_symbols_icons.dart"; import "package:styled_widget/styled_widget.dart"; import "package:super_sliver_list/super_sliver_list.dart"; diff --git a/lib/widgets/chat/room_app_bar.dart b/lib/chat/chat_widgets/room_app_bar.dart similarity index 95% rename from lib/widgets/chat/room_app_bar.dart rename to lib/chat/chat_widgets/room_app_bar.dart index fba7f084..58ae1ee6 100644 --- a/lib/widgets/chat/room_app_bar.dart +++ b/lib/chat/chat_widgets/room_app_bar.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/chat/room_message_list.dart b/lib/chat/chat_widgets/room_message_list.dart similarity index 96% rename from lib/widgets/chat/room_message_list.dart rename to lib/chat/chat_widgets/room_message_list.dart index 163f12b2..432e0f29 100644 --- a/lib/widgets/chat/room_message_list.dart +++ b/lib/chat/chat_widgets/room_message_list.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/database/message.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/screens/chat/widgets/message_item_wrapper.dart'; +import 'package:island/chat/chat_widgets/message_item_wrapper.dart'; +import 'package:island/data/message.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/core/config.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; class RoomMessageList extends HookConsumerWidget { diff --git a/lib/widgets/chat/room_overlays.dart b/lib/chat/chat_widgets/room_overlays.dart similarity index 96% rename from lib/widgets/chat/room_overlays.dart rename to lib/chat/chat_widgets/room_overlays.dart index 2285f225..1bb0d64d 100644 --- a/lib/widgets/chat/room_overlays.dart +++ b/lib/chat/chat_widgets/room_overlays.dart @@ -1,8 +1,8 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/widgets/chat/call_overlay.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/chat/chat_widgets/call_overlay.dart'; import 'package:styled_widget/styled_widget.dart'; class RoomOverlays extends ConsumerWidget { diff --git a/lib/widgets/chat/room_selection_mode.dart b/lib/chat/chat_widgets/room_selection_mode.dart similarity index 100% rename from lib/widgets/chat/room_selection_mode.dart rename to lib/chat/chat_widgets/room_selection_mode.dart diff --git a/lib/hooks/use_room_file_picker.dart b/lib/chat/hooks/use_room_file_picker.dart similarity index 96% rename from lib/hooks/use_room_file_picker.dart rename to lib/chat/hooks/use_room_file_picker.dart index 66a9c4b1..436595fe 100644 --- a/lib/hooks/use_room_file_picker.dart +++ b/lib/chat/hooks/use_room_file_picker.dart @@ -3,8 +3,8 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:island/models/file.dart'; -import 'package:island/widgets/chat/chat_link_attachments.dart'; +import 'package:island/chat/chat_widgets/chat_link_attachments.dart'; +import 'package:island/drive/drive_models/file.dart'; class RoomFilePicker { final List attachments; diff --git a/lib/hooks/use_room_input.dart b/lib/chat/hooks/use_room_input.dart similarity index 94% rename from lib/hooks/use_room_input.dart rename to lib/chat/hooks/use_room_input.dart index 2a03d390..71c283db 100644 --- a/lib/hooks/use_room_input.dart +++ b/lib/chat/hooks/use_room_input.dart @@ -3,14 +3,14 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/models/poll.dart'; -import 'package:island/models/wallet.dart'; -import 'package:island/pods/chat/chat_subscribe.dart'; -import 'package:island/database/message.dart'; -import 'package:island/pods/chat/messages_notifier.dart'; -import 'package:island/widgets/chat/message_item.dart'; +import 'package:island/chat/chat_widgets/message_item.dart'; +import 'package:island/chat/messages_notifier.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/posts/posts_models/poll.dart'; +import 'package:island/wallet/wallet_models/wallet.dart'; +import 'package:island/chat/chat_pod/chat_subscribe.dart'; +import 'package:island/data/message.dart'; import 'package:pasteboard/pasteboard.dart'; class RoomInputManager { diff --git a/lib/hooks/use_room_scroll.dart b/lib/chat/hooks/use_room_scroll.dart similarity index 96% rename from lib/hooks/use_room_scroll.dart rename to lib/chat/hooks/use_room_scroll.dart index 681bec74..56fa8ea0 100644 --- a/lib/hooks/use_room_scroll.dart +++ b/lib/chat/hooks/use_room_scroll.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/chat/chat_room.dart'; -import 'package:island/database/message.dart'; -import 'package:island/pods/chat/messages_notifier.dart'; +import 'package:island/chat/chat_pod/chat_room.dart'; +import 'package:island/chat/messages_notifier.dart'; +import 'package:island/data/message.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; class RoomScrollManager { diff --git a/lib/pods/chat/messages_notifier.dart b/lib/chat/messages_notifier.dart similarity index 98% rename from lib/pods/chat/messages_notifier.dart rename to lib/chat/messages_notifier.dart index 0c5765b5..16bf3a8b 100644 --- a/lib/pods/chat/messages_notifier.dart +++ b/lib/chat/messages_notifier.dart @@ -4,23 +4,23 @@ import "package:drift/drift.dart" show Variable; import "package:easy_localization/easy_localization.dart"; import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:island/database/drift_db.dart"; -import "package:island/database/message.dart"; -import "package:island/models/account.dart"; -import "package:island/models/chat.dart"; -import "package:island/models/file.dart"; -import "package:island/models/poll.dart"; -import "package:island/models/wallet.dart"; -import "package:island/pods/chat/chat_room.dart"; -import "package:island/pods/database.dart"; -import "package:island/pods/lifecycle.dart"; -import "package:island/pods/network.dart"; -import "package:island/services/file_uploader.dart"; +import "package:island/data/drift_db.dart"; +import "package:island/data/message.dart"; +import "package:island/accounts/accounts_models/account.dart"; +import "package:island/chat/chat_models/chat.dart"; +import "package:island/drive/drive_models/file.dart"; +import "package:island/posts/posts_models/poll.dart"; +import "package:island/wallet/wallet_models/wallet.dart"; +import "package:island/chat/chat_pod/chat_room.dart"; +import "package:island/core/database.dart"; +import "package:island/core/lifecycle.dart"; +import "package:island/core/network.dart"; +import "package:island/drive/drive_service.dart"; import "package:island/talker.dart"; -import "package:island/widgets/alert.dart"; +import "package:island/shared/widgets/alert.dart"; import "package:riverpod_annotation/riverpod_annotation.dart"; import "package:uuid/uuid.dart"; -import "package:island/screens/account/profile.dart"; +import "package:island/accounts/account/profile.dart"; part 'messages_notifier.g.dart'; diff --git a/lib/pods/chat/messages_notifier.g.dart b/lib/chat/messages_notifier.g.dart similarity index 100% rename from lib/pods/chat/messages_notifier.g.dart rename to lib/chat/messages_notifier.g.dart diff --git a/lib/widgets/cmp/pattle.dart b/lib/command_palette/palette.dart similarity index 97% rename from lib/widgets/cmp/pattle.dart rename to lib/command_palette/palette.dart index 57c141be..0349a7d8 100644 --- a/lib/widgets/cmp/pattle.dart +++ b/lib/command_palette/palette.dart @@ -7,25 +7,25 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/models/route_item.dart'; -import 'package:island/pods/chat/chat_room.dart'; -import 'package:island/pods/chat/chat_summary.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/userinfo.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/core/models/route_item.dart'; +import 'package:island/chat/chat_pod/chat_room.dart'; +import 'package:island/chat/chat_pod/chat_summary.dart'; +import 'package:island/core/config.dart'; +import 'package:island/accounts/accounts_pod.dart'; import 'package:island/route.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/chat_room_widgets.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/chat/chat_widgets/chat_room_widgets.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:relative_time/relative_time.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:island/services/event_bus.dart'; +import 'package:island/core/services/event_bus.dart'; import 'package:url_launcher/url_launcher.dart'; -class CommandPattleWidget extends HookConsumerWidget { +class CommandPaletteWidget extends HookConsumerWidget { final VoidCallback onDismiss; - const CommandPattleWidget({super.key, required this.onDismiss}); + const CommandPaletteWidget({super.key, required this.onDismiss}); static List _getSpecialActions(BuildContext context) { return [ diff --git a/lib/pods/audio.dart b/lib/core/audio.dart similarity index 97% rename from lib/pods/audio.dart rename to lib/core/audio.dart index 02a112c4..1cbc9ad9 100644 --- a/lib/pods/audio.dart +++ b/lib/core/audio.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:just_audio/just_audio.dart'; -import 'package:island/pods/config.dart'; +import 'package:island/core/config.dart'; import 'package:audio_session/audio_session.dart'; final sfxPlayerProvider = Provider((ref) { diff --git a/lib/pods/config.dart b/lib/core/config.dart similarity index 99% rename from lib/pods/config.dart rename to lib/core/config.dart index 46faf414..16aec621 100644 --- a/lib/pods/config.dart +++ b/lib/core/config.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/services/analytics_service.dart'; +import 'package:island/core/services/analytics_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:window_manager/window_manager.dart'; diff --git a/lib/pods/config.freezed.dart b/lib/core/config.freezed.dart similarity index 100% rename from lib/pods/config.freezed.dart rename to lib/core/config.freezed.dart diff --git a/lib/pods/config.g.dart b/lib/core/config.g.dart similarity index 100% rename from lib/pods/config.g.dart rename to lib/core/config.g.dart diff --git a/lib/widgets/data_saving_gate.dart b/lib/core/data_saving_gate.dart similarity index 93% rename from lib/widgets/data_saving_gate.dart rename to lib/core/data_saving_gate.dart index fc60c2d2..a2204c96 100644 --- a/lib/widgets/data_saving_gate.dart +++ b/lib/core/data_saving_gate.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/config.dart'; +import 'package:island/core/config.dart'; typedef WidgetBuilder = Widget Function(); diff --git a/lib/pods/message.dart b/lib/core/database.dart similarity index 71% rename from lib/pods/message.dart rename to lib/core/database.dart index 6d2728b4..afdd3687 100644 --- a/lib/pods/message.dart +++ b/lib/core/database.dart @@ -2,10 +2,19 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/database.dart'; +import 'package:island/data/drift_db.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:island/data/database.native.dart' + if (dart.library.html) 'package:island/data/database.web.dart'; + +final databaseProvider = Provider((ref) { + final db = constructDb(); + ref.onDispose(() => db.close()); + return db; +}); + Future resetDatabase(WidgetRef ref) async { if (kIsWeb) return; diff --git a/lib/widgets/debug_sheet.dart b/lib/core/debug_sheet.dart similarity index 86% rename from lib/widgets/debug_sheet.dart rename to lib/core/debug_sheet.dart index 0a2a217f..ee49dfa6 100644 --- a/lib/widgets/debug_sheet.dart +++ b/lib/core/debug_sheet.dart @@ -3,16 +3,16 @@ import 'package:flutter/services.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/message.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/services/fitness_data.dart'; -import 'package:island/services/fitness_service.dart'; -import 'package:island/services/update_service.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/network_status_sheet.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/core/database.dart'; +import 'package:island/core/network.dart'; +import 'package:island/fitness/fitness_data.dart'; +import 'package:island/fitness/fitness_service.dart'; +import 'package:island/core/services/update_service.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/network_status_sheet.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:island/pods/config.dart'; +import 'package:island/core/config.dart'; import 'package:talker_flutter/talker_flutter.dart'; import 'package:island/talker.dart'; @@ -208,50 +208,59 @@ class DebugSheet extends HookConsumerWidget { onTap: () async { try { final fitnessService = ref.read(fitnessServiceProvider); - + // Check if platform is supported if (!fitnessService.isPlatformSupported) { - showErrorAlert('Fitness data is only available on iOS and Android devices.'); + showErrorAlert( + 'Fitness data is only available on iOS and Android devices.', + ); return; } // Check permissions first - final permissionStatus = await fitnessService.getPermissionStatus(); + final permissionStatus = await fitnessService + .getPermissionStatus(); if (permissionStatus != FitnessPermissionStatus.granted) { final granted = await fitnessService.requestPermissions(); if (!granted) { - showErrorAlert('Permission to access fitness data was denied. Please enable it in your device settings.'); + showErrorAlert( + 'Permission to access fitness data was denied. Please enable it in your device settings.', + ); return; } } // Get workouts from the last 7 days final workouts = await fitnessService.getWorkoutsLast7Days(); - + if (workouts.isEmpty) { - showInfoAlert('No workout data found for the last 7 days.', 'No Data'); + showInfoAlert( + 'No workout data found for the last 7 days.', + 'No Data', + ); return; } // Format the workout data for display StringBuffer sb = StringBuffer(); for (final workout in workouts) { - final dateStr = '${workout.startTime.day}/${workout.startTime.month}'; - final energyStr = workout.energyBurnedString.isNotEmpty - ? ' • ${workout.energyBurnedString}' + final dateStr = + '${workout.startTime.day}/${workout.startTime.month}'; + final energyStr = workout.energyBurnedString.isNotEmpty + ? ' • ${workout.energyBurnedString}' : ''; - final distanceStr = workout.distanceString.isNotEmpty - ? ' • ${workout.distanceString}' + final distanceStr = workout.distanceString.isNotEmpty + ? ' • ${workout.distanceString}' : ''; - final stepsStr = workout.stepsString.isNotEmpty - ? ' • ${workout.stepsString} steps' + final stepsStr = workout.stepsString.isNotEmpty + ? ' • ${workout.stepsString} steps' : ''; - + sb.write( '${workout.workoutTypeString} • $dateStr • ${workout.durationString}$energyStr$distanceStr$stepsStr\n', ); } - + showInfoAlert(sb.toString(), 'Workout Data Retrieved'); } catch (e) { showErrorAlert(e); diff --git a/lib/pods/lifecycle.dart b/lib/core/lifecycle.dart similarity index 100% rename from lib/pods/lifecycle.dart rename to lib/core/lifecycle.dart diff --git a/lib/pods/link_preview.dart b/lib/core/link_preview.dart similarity index 86% rename from lib/pods/link_preview.dart rename to lib/core/link_preview.dart index 02f1627a..88bf93a3 100644 --- a/lib/pods/link_preview.dart +++ b/lib/core/link_preview.dart @@ -1,6 +1,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:island/models/embed.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/posts/posts_models/embed.dart'; +import 'package:island/core/network.dart'; part 'link_preview.g.dart'; diff --git a/lib/pods/link_preview.g.dart b/lib/core/link_preview.g.dart similarity index 100% rename from lib/pods/link_preview.g.dart rename to lib/core/link_preview.g.dart diff --git a/lib/models/activity.dart b/lib/core/models/activity.dart similarity index 97% rename from lib/models/activity.dart rename to lib/core/models/activity.dart index 3ebc9717..95633522 100644 --- a/lib/models/activity.dart +++ b/lib/core/models/activity.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/account.dart'; +import 'package:island/accounts/accounts_models/account.dart'; part 'activity.freezed.dart'; part 'activity.g.dart'; diff --git a/lib/models/activity.freezed.dart b/lib/core/models/activity.freezed.dart similarity index 100% rename from lib/models/activity.freezed.dart rename to lib/core/models/activity.freezed.dart diff --git a/lib/models/activity.g.dart b/lib/core/models/activity.g.dart similarity index 100% rename from lib/models/activity.g.dart rename to lib/core/models/activity.g.dart diff --git a/lib/models/activitypub.dart b/lib/core/models/activitypub.dart similarity index 100% rename from lib/models/activitypub.dart rename to lib/core/models/activitypub.dart diff --git a/lib/models/activitypub.freezed.dart b/lib/core/models/activitypub.freezed.dart similarity index 100% rename from lib/models/activitypub.freezed.dart rename to lib/core/models/activitypub.freezed.dart diff --git a/lib/models/activitypub.g.dart b/lib/core/models/activitypub.g.dart similarity index 100% rename from lib/models/activitypub.g.dart rename to lib/core/models/activitypub.g.dart diff --git a/lib/models/route_item.dart b/lib/core/models/route_item.dart similarity index 100% rename from lib/models/route_item.dart rename to lib/core/models/route_item.dart diff --git a/lib/models/route_item.freezed.dart b/lib/core/models/route_item.freezed.dart similarity index 100% rename from lib/models/route_item.freezed.dart rename to lib/core/models/route_item.freezed.dart diff --git a/lib/widgets/navigation/conditional_bottom_nav.dart b/lib/core/navigation/conditional_bottom_nav.dart similarity index 89% rename from lib/widgets/navigation/conditional_bottom_nav.dart rename to lib/core/navigation/conditional_bottom_nav.dart index 0c8710a9..4d8e5710 100644 --- a/lib/widgets/navigation/conditional_bottom_nav.dart +++ b/lib/core/navigation/conditional_bottom_nav.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/screens/tabs.dart'; -import 'package:island/services/responsive.dart'; +import 'package:island/settings/tabs_screen.dart'; +import 'package:island/core/services/responsive.dart'; class ConditionalBottomNav extends HookConsumerWidget { final Widget child; diff --git a/lib/pods/network.dart b/lib/core/network.dart similarity index 99% rename from lib/pods/network.dart rename to lib/core/network.dart index 42bce98a..eae6fcd0 100644 --- a/lib/pods/network.dart +++ b/lib/core/network.dart @@ -10,7 +10,7 @@ import 'package:dio/dio.dart'; import 'package:image_picker/image_picker.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart'; -import 'package:island/models/auth.dart'; +import 'package:island/auth/auth_models/auth.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:talker_dio_logger/talker_dio_logger.dart'; import 'package:island/talker.dart'; diff --git a/lib/pods/network.g.dart b/lib/core/network.g.dart similarity index 100% rename from lib/pods/network.g.dart rename to lib/core/network.g.dart diff --git a/lib/pods/notification.dart b/lib/core/notification.dart similarity index 97% rename from lib/pods/notification.dart rename to lib/core/notification.dart index ebb2f7d9..460fa89e 100644 --- a/lib/pods/notification.dart +++ b/lib/core/notification.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:island/models/account.dart'; +import 'package:island/accounts/accounts_models/account.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:uuid/uuid.dart'; diff --git a/lib/pods/notification.g.dart b/lib/core/notification.g.dart similarity index 100% rename from lib/pods/notification.g.dart rename to lib/core/notification.g.dart diff --git a/lib/services/activitypub_service.dart b/lib/core/services/activitypub_service.dart similarity index 96% rename from lib/services/activitypub_service.dart rename to lib/core/services/activitypub_service.dart index 63f7da18..02e70f8b 100644 --- a/lib/services/activitypub_service.dart +++ b/lib/core/services/activitypub_service.dart @@ -1,7 +1,7 @@ import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:island/models/activitypub.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/core/models/activitypub.dart'; +import 'package:island/core/network.dart'; final activityPubServiceProvider = Provider((ref) { final client = ref.watch(apiClientProvider); diff --git a/lib/services/analytics_service.dart b/lib/core/services/analytics_service.dart similarity index 100% rename from lib/services/analytics_service.dart rename to lib/core/services/analytics_service.dart diff --git a/lib/services/color.dart b/lib/core/services/color.dart similarity index 100% rename from lib/services/color.dart rename to lib/core/services/color.dart diff --git a/lib/services/color_extraction.dart b/lib/core/services/color_extraction.dart similarity index 100% rename from lib/services/color_extraction.dart rename to lib/core/services/color_extraction.dart diff --git a/lib/services/event_bus.dart b/lib/core/services/event_bus.dart similarity index 100% rename from lib/services/event_bus.dart rename to lib/core/services/event_bus.dart diff --git a/lib/services/file.dart b/lib/core/services/image.dart similarity index 90% rename from lib/services/file.dart rename to lib/core/services/image.dart index 2b1a329f..6df37321 100644 --- a/lib/services/file.dart +++ b/lib/core/services/image.dart @@ -1,8 +1,8 @@ -import 'dart:async'; import 'dart:ui'; + import 'package:croppy/croppy.dart'; -import 'package:cross_file/cross_file.dart'; -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; Future cropImage( BuildContext context, { diff --git a/lib/services/notify.dart b/lib/core/services/notify.dart similarity index 100% rename from lib/services/notify.dart rename to lib/core/services/notify.dart diff --git a/lib/services/notify.universal.dart b/lib/core/services/notify.universal.dart similarity index 96% rename from lib/services/notify.universal.dart rename to lib/core/services/notify.universal.dart index c1086637..db46c200 100644 --- a/lib/services/notify.universal.dart +++ b/lib/core/services/notify.universal.dart @@ -8,12 +8,12 @@ import 'package:flutter/services.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:island/pods/audio.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/notification.dart'; +import 'package:island/core/audio.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/notification.dart'; import 'package:island/route.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/websocket.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/websocket.dart'; import 'package:island/talker.dart'; import 'package:url_launcher/url_launcher_string.dart'; diff --git a/lib/services/notify.windows.dart b/lib/core/services/notify.windows.dart similarity index 95% rename from lib/services/notify.windows.dart rename to lib/core/services/notify.windows.dart index 2cad470f..2a852ca9 100644 --- a/lib/services/notify.windows.dart +++ b/lib/core/services/notify.windows.dart @@ -6,11 +6,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:island/pods/audio.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/notification.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/websocket.dart'; +import 'package:island/core/audio.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/notification.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/websocket.dart'; import 'package:island/talker.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:windows_notification/windows_notification.dart' as winty; diff --git a/lib/services/quick_actions.dart b/lib/core/services/quick_actions.dart similarity index 97% rename from lib/services/quick_actions.dart rename to lib/core/services/quick_actions.dart index 61ae7cd1..1af48bc1 100644 --- a/lib/services/quick_actions.dart +++ b/lib/core/services/quick_actions.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:go_router/go_router.dart'; import 'package:island/route.dart'; -import 'package:island/services/event_bus.dart'; +import 'package:island/core/services/event_bus.dart'; import 'package:island/talker.dart'; import 'package:quick_actions/quick_actions.dart'; diff --git a/lib/services/responsive.dart b/lib/core/services/responsive.dart similarity index 100% rename from lib/services/responsive.dart rename to lib/core/services/responsive.dart diff --git a/lib/services/sharing_intent.dart b/lib/core/services/sharing_intent.dart similarity index 98% rename from lib/services/sharing_intent.dart rename to lib/core/services/sharing_intent.dart index b77bac6a..f72f23cc 100644 --- a/lib/services/sharing_intent.dart +++ b/lib/core/services/sharing_intent.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; -import 'package:island/widgets/share/share_sheet.dart'; +import 'package:island/core/widgets/share/share_sheet.dart'; import 'package:share_plus/share_plus.dart'; class SharingIntentService { diff --git a/lib/services/time.dart b/lib/core/services/time.dart similarity index 100% rename from lib/services/time.dart rename to lib/core/services/time.dart diff --git a/lib/services/timezone.dart b/lib/core/services/timezone.dart similarity index 100% rename from lib/services/timezone.dart rename to lib/core/services/timezone.dart diff --git a/lib/services/timezone/native.dart b/lib/core/services/timezone/native.dart similarity index 100% rename from lib/services/timezone/native.dart rename to lib/core/services/timezone/native.dart diff --git a/lib/services/timezone/web.dart b/lib/core/services/timezone/web.dart similarity index 100% rename from lib/services/timezone/web.dart rename to lib/core/services/timezone/web.dart diff --git a/lib/services/tour.dart b/lib/core/services/tour.dart similarity index 97% rename from lib/services/tour.dart rename to lib/core/services/tour.dart index b36d64ad..4487e555 100644 --- a/lib/services/tour.dart +++ b/lib/core/services/tour.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:flutter/widgets.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/pods/config.dart'; +import 'package:island/core/config.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'tour.g.dart'; diff --git a/lib/services/tour.freezed.dart b/lib/core/services/tour.freezed.dart similarity index 100% rename from lib/services/tour.freezed.dart rename to lib/core/services/tour.freezed.dart diff --git a/lib/services/tour.g.dart b/lib/core/services/tour.g.dart similarity index 100% rename from lib/services/tour.g.dart rename to lib/core/services/tour.g.dart diff --git a/lib/services/udid.dart b/lib/core/services/udid.dart similarity index 100% rename from lib/services/udid.dart rename to lib/core/services/udid.dart diff --git a/lib/services/udid.native.dart b/lib/core/services/udid.native.dart similarity index 100% rename from lib/services/udid.native.dart rename to lib/core/services/udid.native.dart diff --git a/lib/services/udid.web.dart b/lib/core/services/udid.web.dart similarity index 100% rename from lib/services/udid.web.dart rename to lib/core/services/udid.web.dart diff --git a/lib/services/update_service.dart b/lib/core/services/update_service.dart similarity index 99% rename from lib/services/update_service.dart rename to lib/core/services/update_service.dart index 97db76f2..203bcab4 100644 --- a/lib/services/update_service.dart +++ b/lib/core/services/update_service.dart @@ -8,7 +8,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_app_update/azhon_app_update.dart'; import 'package:flutter_app_update/update_model.dart'; -import 'package:island/widgets/content/markdown.dart'; +import 'package:island/core/widgets/content/markdown.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; @@ -17,7 +17,7 @@ import 'package:process_run/process_run.dart'; import 'package:collection/collection.dart'; // Added for firstWhereOrNull import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:island/talker.dart'; /// Data model for a GitHub release we care about diff --git a/lib/services/widget_sync_service.dart b/lib/core/services/widget_sync_service.dart similarity index 100% rename from lib/services/widget_sync_service.dart rename to lib/core/services/widget_sync_service.dart diff --git a/lib/pods/theme.dart b/lib/core/theme.dart similarity index 98% rename from lib/pods/theme.dart rename to lib/core/theme.dart index 58d02d8d..7f25d3e2 100644 --- a/lib/pods/theme.dart +++ b/lib/core/theme.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:island/pods/config.dart'; +import 'package:island/core/config.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'theme.g.dart'; diff --git a/lib/pods/theme.g.dart b/lib/core/theme.g.dart similarity index 100% rename from lib/pods/theme.g.dart rename to lib/core/theme.g.dart diff --git a/lib/widgets/tour/techincal_review_intro.dart b/lib/core/tour/techincal_review_intro.dart similarity index 96% rename from lib/widgets/tour/techincal_review_intro.dart rename to lib/core/tour/techincal_review_intro.dart index b665ae7e..cfa0c983 100644 --- a/lib/widgets/tour/techincal_review_intro.dart +++ b/lib/core/tour/techincal_review_intro.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:styled_widget/styled_widget.dart'; class TechicalReviewIntroWidget extends StatelessWidget { diff --git a/lib/widgets/tour/tour.dart b/lib/core/tour/tour.dart similarity index 95% rename from lib/widgets/tour/tour.dart rename to lib/core/tour/tour.dart index ba7f517a..3c32231a 100644 --- a/lib/widgets/tour/tour.dart +++ b/lib/core/tour/tour.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/services/tour.dart'; +import 'package:island/core/services/tour.dart'; const List kStartTours = ['technical_review_intro']; diff --git a/lib/pods/translate.dart b/lib/core/translate.dart similarity index 96% rename from lib/pods/translate.dart rename to lib/core/translate.dart index 7b1529b9..43fb295a 100644 --- a/lib/pods/translate.dart +++ b/lib/core/translate.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/core/network.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'translate.freezed.dart'; diff --git a/lib/pods/translate.freezed.dart b/lib/core/translate.freezed.dart similarity index 100% rename from lib/pods/translate.freezed.dart rename to lib/core/translate.freezed.dart diff --git a/lib/pods/translate.g.dart b/lib/core/translate.g.dart similarity index 100% rename from lib/pods/translate.g.dart rename to lib/core/translate.g.dart diff --git a/lib/utils/abuse_report_utils.dart b/lib/core/utils/abuse_report_utils.dart similarity index 100% rename from lib/utils/abuse_report_utils.dart rename to lib/core/utils/abuse_report_utils.dart diff --git a/lib/utils/activity_utils.dart b/lib/core/utils/activity_utils.dart similarity index 98% rename from lib/utils/activity_utils.dart rename to lib/core/utils/activity_utils.dart index 8cc79532..5ed5818c 100644 --- a/lib/utils/activity_utils.dart +++ b/lib/core/utils/activity_utils.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:island/models/account.dart'; +import 'package:island/accounts/accounts_models/account.dart'; String? getActivityTitle(String? label, Map? meta) { if (meta == null) return label; diff --git a/lib/utils/file_icon_utils.dart b/lib/core/utils/file_icon_utils.dart similarity index 95% rename from lib/utils/file_icon_utils.dart rename to lib/core/utils/file_icon_utils.dart index 7e1e53df..d79312d0 100644 --- a/lib/utils/file_icon_utils.dart +++ b/lib/core/utils/file_icon_utils.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; -import '../models/file.dart'; -import '../widgets/content/cloud_files.dart'; - /// Returns an appropriate icon widget for the given file based on its MIME type Widget getFileIcon( SnCloudFile file, { diff --git a/lib/utils/format.dart b/lib/core/utils/format.dart similarity index 100% rename from lib/utils/format.dart rename to lib/core/utils/format.dart diff --git a/lib/utils/mapping.dart b/lib/core/utils/mapping.dart similarity index 100% rename from lib/utils/mapping.dart rename to lib/core/utils/mapping.dart diff --git a/lib/utils/share_utils.dart b/lib/core/utils/share_utils.dart similarity index 85% rename from lib/utils/share_utils.dart rename to lib/core/utils/share_utils.dart index 6e4575b8..94b5945d 100644 --- a/lib/utils/share_utils.dart +++ b/lib/core/utils/share_utils.dart @@ -3,15 +3,15 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/post/post_item_screenshot.dart'; -import 'package:island/widgets/post/post_shared.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/core/config.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/posts/posts_widgets/post/post_item_screenshot.dart'; +import 'package:island/posts/posts_widgets/post/post_shared.dart'; import 'package:path_provider/path_provider.dart' show getTemporaryDirectory; import 'package:screenshot/screenshot.dart'; import 'package:share_plus/share_plus.dart'; -import 'package:island/services/analytics_service.dart'; +import 'package:island/core/services/analytics_service.dart'; /// Shares a post as a screenshot image Future sharePostAsScreenshot( diff --git a/lib/utils/text.dart b/lib/core/utils/text.dart similarity index 100% rename from lib/utils/text.dart rename to lib/core/utils/text.dart diff --git a/lib/pods/websocket.dart b/lib/core/websocket.dart similarity index 98% rename from lib/pods/websocket.dart rename to lib/core/websocket.dart index 44bb74f5..39c1c3ce 100644 --- a/lib/pods/websocket.dart +++ b/lib/core/websocket.dart @@ -3,8 +3,8 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:island/talker.dart'; diff --git a/lib/pods/websocket.freezed.dart b/lib/core/websocket.freezed.dart similarity index 100% rename from lib/pods/websocket.freezed.dart rename to lib/core/websocket.freezed.dart diff --git a/lib/pods/websocket.g.dart b/lib/core/websocket.g.dart similarity index 100% rename from lib/pods/websocket.g.dart rename to lib/core/websocket.g.dart diff --git a/lib/widgets/content/attachment_preview.dart b/lib/core/widgets/content/attachment_preview.dart similarity index 98% rename from lib/widgets/content/attachment_preview.dart rename to lib/core/widgets/content/attachment_preview.dart index 3223d1d7..19bb2065 100644 --- a/lib/widgets/content/attachment_preview.dart +++ b/lib/core/widgets/content/attachment_preview.dart @@ -3,21 +3,21 @@ import 'dart:io'; import 'package:cross_file/cross_file.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:island/core/services/image.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/services/file.dart'; -import 'package:island/services/file_uploader.dart'; -import 'package:island/utils/format.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sensitive.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/core/network.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/core/utils/format.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sensitive.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:super_context_menu/super_context_menu.dart'; diff --git a/lib/widgets/content/audio.dart b/lib/core/widgets/content/audio.dart similarity index 97% rename from lib/widgets/content/audio.dart rename to lib/core/widgets/content/audio.dart index 514848c5..954b52a1 100644 --- a/lib/widgets/content/audio.dart +++ b/lib/core/widgets/content/audio.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/services/time.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/time.dart'; import 'package:island/talker.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:media_kit/media_kit.dart'; diff --git a/lib/widgets/content/cloud_file_collection.dart b/lib/core/widgets/content/cloud_file_collection.dart similarity index 98% rename from lib/widgets/content/cloud_file_collection.dart rename to lib/core/widgets/content/cloud_file_collection.dart index 7d5e4f5d..b48d738f 100644 --- a/lib/widgets/content/cloud_file_collection.dart +++ b/lib/core/widgets/content/cloud_file_collection.dart @@ -7,11 +7,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/cloud_file_lightbox.dart'; -import 'package:island/widgets/content/sensitive.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/core/config.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/cloud_file_lightbox.dart'; +import 'package:island/core/widgets/content/sensitive.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:uuid/uuid.dart'; diff --git a/lib/widgets/content/cloud_file_lightbox.dart b/lib/core/widgets/content/cloud_file_lightbox.dart similarity index 91% rename from lib/widgets/content/cloud_file_lightbox.dart rename to lib/core/widgets/content/cloud_file_lightbox.dart index 49616698..9f21139a 100644 --- a/lib/widgets/content/cloud_file_lightbox.dart +++ b/lib/core/widgets/content/cloud_file_lightbox.dart @@ -4,14 +4,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/services/file_download.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/exif_info_overlay.dart'; -import 'package:island/widgets/content/file_action_button.dart'; -import 'package:island/widgets/content/file_info_sheet.dart'; -import 'package:island/widgets/content/image_control_overlay.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/core/config.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/exif_info_overlay.dart'; +import 'package:island/core/widgets/content/file_action_button.dart'; +import 'package:island/core/widgets/content/file_info_sheet.dart'; +import 'package:island/core/widgets/content/image_control_overlay.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:photo_view/photo_view.dart'; diff --git a/lib/widgets/content/cloud_file_picker.dart b/lib/core/widgets/content/cloud_file_picker.dart similarity index 82% rename from lib/widgets/content/cloud_file_picker.dart rename to lib/core/widgets/content/cloud_file_picker.dart index 0521afbc..bb6f57f6 100644 --- a/lib/widgets/content/cloud_file_picker.dart +++ b/lib/core/widgets/content/cloud_file_picker.dart @@ -5,10 +5,10 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:island/models/file.dart'; -import 'package:island/services/file_uploader.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/attachment_preview.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/attachment_preview.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -57,14 +57,13 @@ class CloudFilePicker extends HookConsumerWidget { for (var idx = 0; idx < files.value.length; idx++) { uploadPosition.value = idx; final file = files.value[idx]; - final cloudFile = - await FileUploader.createCloudFile( - fileData: file, - ref: ref, - onProgress: (progress, _) { - uploadProgress.value = progress; - }, - ).future; + final cloudFile = await FileUploader.createCloudFile( + fileData: file, + ref: ref, + onProgress: (progress, _) { + uploadProgress.value = progress; + }, + ).future; if (cloudFile == null) { throw ArgumentError('Failed to upload the file...'); } @@ -87,14 +86,12 @@ class CloudFilePicker extends HookConsumerWidget { return; } - final newFiles = - result.files.map((e) { - final xfile = - e.bytes != null - ? XFile.fromData(e.bytes!, name: e.name) - : XFile(e.path!); - return UniversalFile(data: xfile, type: UniversalFileType.file); - }).toList(); + final newFiles = result.files.map((e) { + final xfile = e.bytes != null + ? XFile.fromData(e.bytes!, name: e.name) + : XFile(e.path!); + return UniversalFile(data: xfile, type: UniversalFileType.file); + }).toList(); if (!allowMultiple) { files.value = newFiles; @@ -126,13 +123,12 @@ class CloudFilePicker extends HookConsumerWidget { return; } - final newFiles = - results - .map( - (xfile) => - UniversalFile(data: xfile, type: UniversalFileType.image), - ) - .toList(); + final newFiles = results + .map( + (xfile) => + UniversalFile(data: xfile, type: UniversalFileType.image), + ) + .toList(); if (!allowMultiple) { files.value = newFiles; @@ -158,14 +154,12 @@ class CloudFilePicker extends HookConsumerWidget { return; } - final newFiles = - result.files.map((e) { - final xfile = - e.bytes != null - ? XFile.fromData(e.bytes!, name: e.name) - : XFile(e.path!); - return UniversalFile(data: xfile, type: UniversalFileType.video); - }).toList(); + final newFiles = result.files.map((e) { + final xfile = e.bytes != null + ? XFile.fromData(e.bytes!, name: e.name) + : XFile(e.path!); + return UniversalFile(data: xfile, type: UniversalFileType.video); + }).toList(); if (!allowMultiple) { files.value = newFiles; @@ -230,8 +224,9 @@ class CloudFilePicker extends HookConsumerWidget { LinearProgressIndicator( value: uploadOverallProgress, color: Theme.of(context).colorScheme.primary, - backgroundColor: - Theme.of(context).colorScheme.surfaceVariant, + backgroundColor: Theme.of( + context, + ).colorScheme.surfaceVariant, ), ], ), @@ -252,16 +247,15 @@ class CloudFilePicker extends HookConsumerWidget { itemCount: files.value.length, itemBuilder: (context, idx) { return AttachmentPreview( - onDelete: - uploadOverallProgress != null - ? null - : () { - files.value = [ - ...files.value.where( - (e) => e != files.value[idx], - ), - ]; - }, + onDelete: uploadOverallProgress != null + ? null + : () { + files.value = [ + ...files.value.where( + (e) => e != files.value[idx], + ), + ]; + }, item: files.value[idx], progress: null, ); diff --git a/lib/widgets/content/embed/embed_list.dart b/lib/core/widgets/content/embed/embed_list.dart similarity index 94% rename from lib/widgets/content/embed/embed_list.dart rename to lib/core/widgets/content/embed/embed_list.dart index 8c40ab0d..1df478a7 100644 --- a/lib/widgets/content/embed/embed_list.dart +++ b/lib/core/widgets/content/embed/embed_list.dart @@ -1,9 +1,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:island/models/embed.dart'; -import 'package:island/widgets/content/embed/link.dart'; -import 'package:island/widgets/poll/poll_submit.dart'; -import 'package:island/widgets/wallet/fund_envelope.dart'; +import 'package:island/polls/polls_widgets/poll/poll_submit.dart'; +import 'package:island/posts/posts_models/embed.dart'; +import 'package:island/core/widgets/content/embed/link.dart'; +import 'package:island/wallet/wallet_widgets/wallet/fund_envelope.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; class EmbedListWidget extends StatelessWidget { diff --git a/lib/widgets/content/embed/link.dart b/lib/core/widgets/content/embed/link.dart similarity index 99% rename from lib/widgets/content/embed/link.dart rename to lib/core/widgets/content/embed/link.dart index 9c6f2bf0..1d92a840 100644 --- a/lib/widgets/content/embed/link.dart +++ b/lib/core/widgets/content/embed/link.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:island/models/embed.dart'; -import 'package:island/widgets/content/image.dart'; +import 'package:island/posts/posts_models/embed.dart'; +import 'package:island/core/widgets/content/image.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/lib/widgets/content/exif_info_overlay.dart b/lib/core/widgets/content/exif_info_overlay.dart similarity index 98% rename from lib/widgets/content/exif_info_overlay.dart rename to lib/core/widgets/content/exif_info_overlay.dart index 2398dbb4..0f2c0693 100644 --- a/lib/widgets/content/exif_info_overlay.dart +++ b/lib/core/widgets/content/exif_info_overlay.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:island/models/file.dart'; +import 'package:island/drive/drive_models/file.dart'; import 'package:material_symbols_icons/symbols.dart'; class ExifInfoOverlay extends StatelessWidget { diff --git a/lib/widgets/content/file_action_button.dart b/lib/core/widgets/content/file_action_button.dart similarity index 100% rename from lib/widgets/content/file_action_button.dart rename to lib/core/widgets/content/file_action_button.dart diff --git a/lib/widgets/content/file_info_sheet.dart b/lib/core/widgets/content/file_info_sheet.dart similarity index 98% rename from lib/widgets/content/file_info_sheet.dart rename to lib/core/widgets/content/file_info_sheet.dart index b08e51db..1eb728ee 100644 --- a/lib/widgets/content/file_info_sheet.dart +++ b/lib/core/widgets/content/file_info_sheet.dart @@ -3,10 +3,10 @@ import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:island/models/file.dart'; -import 'package:island/utils/format.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/core/utils/format.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher_string.dart'; diff --git a/lib/widgets/content/file_viewer_contents.dart b/lib/core/widgets/content/file_viewer_contents.dart similarity index 93% rename from lib/widgets/content/file_viewer_contents.dart rename to lib/core/widgets/content/file_viewer_contents.dart index ca53fa60..520616e9 100644 --- a/lib/widgets/content/file_viewer_contents.dart +++ b/lib/core/widgets/content/file_viewer_contents.dart @@ -7,17 +7,17 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/services/file_download.dart'; -import 'package:island/utils/format.dart'; -import 'package:island/widgets/content/audio.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/exif_info_overlay.dart'; -import 'package:island/widgets/content/file_info_sheet.dart'; -import 'package:island/widgets/content/image_control_overlay.dart'; -import 'package:island/widgets/content/video.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/core/utils/format.dart'; +import 'package:island/core/widgets/content/audio.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/exif_info_overlay.dart'; +import 'package:island/core/widgets/content/file_info_sheet.dart'; +import 'package:island/core/widgets/content/image_control_overlay.dart'; +import 'package:island/core/widgets/content/video.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:photo_view/photo_view.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; diff --git a/lib/widgets/content/image.dart b/lib/core/widgets/content/image.dart similarity index 99% rename from lib/widgets/content/image.dart rename to lib/core/widgets/content/image.dart index bd17c54e..54ec3e66 100644 --- a/lib/widgets/content/image.dart +++ b/lib/core/widgets/content/image.dart @@ -7,8 +7,8 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; class UniversalImage extends HookConsumerWidget { final String uri; diff --git a/lib/widgets/content/image_control_overlay.dart b/lib/core/widgets/content/image_control_overlay.dart similarity index 100% rename from lib/widgets/content/image_control_overlay.dart rename to lib/core/widgets/content/image_control_overlay.dart diff --git a/lib/widgets/content/markdown.dart b/lib/core/widgets/content/markdown.dart similarity index 97% rename from lib/widgets/content/markdown.dart rename to lib/core/widgets/content/markdown.dart index 3b6f450b..bdd947cd 100644 --- a/lib/widgets/content/markdown.dart +++ b/lib/core/widgets/content/markdown.dart @@ -9,16 +9,16 @@ import 'package:flutter_highlight/themes/a11y-light.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/screens/account/profile.dart'; -import 'package:island/screens/posts/publisher_profile.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/cloud_file_lightbox.dart'; -import 'package:island/widgets/content/markdown_latex.dart'; +import 'package:island/accounts/account/profile.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/core/config.dart'; +import 'package:island/posts/publisher_profile.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/cloud_file_lightbox.dart'; +import 'package:island/core/widgets/content/markdown_latex.dart'; import 'package:markdown/markdown.dart' as markdown; import 'package:markdown_widget/markdown_widget.dart'; import 'package:material_symbols_icons/symbols.dart'; diff --git a/lib/widgets/content/markdown_latex.dart b/lib/core/widgets/content/markdown_latex.dart similarity index 100% rename from lib/widgets/content/markdown_latex.dart rename to lib/core/widgets/content/markdown_latex.dart diff --git a/lib/widgets/content/network_status_sheet.dart b/lib/core/widgets/content/network_status_sheet.dart similarity index 97% rename from lib/widgets/content/network_status_sheet.dart rename to lib/core/widgets/content/network_status_sheet.dart index 7c39d84f..cfe3306a 100644 --- a/lib/widgets/content/network_status_sheet.dart +++ b/lib/core/widgets/content/network_status_sheet.dart @@ -2,11 +2,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/websocket.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/websocket.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher_string.dart'; diff --git a/lib/widgets/content/profile_decoration.dart b/lib/core/widgets/content/profile_decoration.dart similarity index 100% rename from lib/widgets/content/profile_decoration.dart rename to lib/core/widgets/content/profile_decoration.dart diff --git a/lib/widgets/content/profile_decoration.freezed.dart b/lib/core/widgets/content/profile_decoration.freezed.dart similarity index 100% rename from lib/widgets/content/profile_decoration.freezed.dart rename to lib/core/widgets/content/profile_decoration.freezed.dart diff --git a/lib/widgets/content/sensitive.dart b/lib/core/widgets/content/sensitive.dart similarity index 100% rename from lib/widgets/content/sensitive.dart rename to lib/core/widgets/content/sensitive.dart diff --git a/lib/widgets/content/sheet.dart b/lib/core/widgets/content/sheet.dart similarity index 100% rename from lib/widgets/content/sheet.dart rename to lib/core/widgets/content/sheet.dart diff --git a/lib/widgets/content/video.dart b/lib/core/widgets/content/video.dart similarity index 100% rename from lib/widgets/content/video.dart rename to lib/core/widgets/content/video.dart diff --git a/lib/widgets/content/video.native.dart b/lib/core/widgets/content/video.native.dart similarity index 95% rename from lib/widgets/content/video.native.dart rename to lib/core/widgets/content/video.native.dart index b6058500..a2026a94 100644 --- a/lib/widgets/content/video.native.dart +++ b/lib/core/widgets/content/video.native.dart @@ -2,8 +2,8 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; diff --git a/lib/widgets/content/video.web.dart b/lib/core/widgets/content/video.web.dart similarity index 100% rename from lib/widgets/content/video.web.dart rename to lib/core/widgets/content/video.web.dart diff --git a/lib/widgets/payment/README.md b/lib/core/widgets/payment/README.md similarity index 100% rename from lib/widgets/payment/README.md rename to lib/core/widgets/payment/README.md diff --git a/lib/widgets/payment/payment_overlay.dart b/lib/core/widgets/payment/payment_overlay.dart similarity index 98% rename from lib/widgets/payment/payment_overlay.dart rename to lib/core/widgets/payment/payment_overlay.dart index 17ed3e74..3daebe80 100644 --- a/lib/widgets/payment/payment_overlay.dart +++ b/lib/core/widgets/payment/payment_overlay.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/widgets/alert.dart'; +import 'package:island/shared/widgets/alert.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:island/models/wallet.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/wallet/wallet_models/wallet.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/core/network.dart'; import 'package:dio/dio.dart'; import 'package:local_auth/local_auth.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; diff --git a/lib/widgets/share/share_sheet.dart b/lib/core/widgets/share/share_sheet.dart similarity index 98% rename from lib/widgets/share/share_sheet.dart rename to lib/core/widgets/share/share_sheet.dart index fc16648c..fccf9c49 100644 --- a/lib/widgets/share/share_sheet.dart +++ b/lib/core/widgets/share/share_sheet.dart @@ -4,21 +4,21 @@ import 'package:flutter/services.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/chat/chat_room.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/services/file_uploader.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/chat/chat_pod/chat_room.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:island/screens/posts/compose.dart'; -import 'package:island/models/file.dart'; -import 'package:island/widgets/post/compose_sheet.dart'; -import 'package:island/pods/link_preview.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/posts/compose.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts_widgets/compose_sheet.dart'; +import 'package:island/core/link_preview.dart'; +import 'package:island/core/network.dart'; import 'package:mime/mime.dart'; import 'package:path/path.dart' as path; -import 'package:island/models/chat.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:share_plus/share_plus.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/shared/upload_menu.dart b/lib/core/widgets/shared/upload_menu.dart similarity index 100% rename from lib/widgets/shared/upload_menu.dart rename to lib/core/widgets/shared/upload_menu.dart diff --git a/lib/screens/creators/hub.dart b/lib/creators/creators/hub.dart similarity index 97% rename from lib/screens/creators/hub.dart rename to lib/creators/creators/hub.dart index 6ffcd527..54273272 100644 --- a/lib/screens/creators/hub.dart +++ b/lib/creators/creators/hub.dart @@ -6,24 +6,24 @@ import 'package:go_router/go_router.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/activitypub.dart'; -import 'package:island/models/post.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/models/heatmap.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/screens/creators/publishers_form.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/utils/text.dart'; -import 'package:island/widgets/account/account_picker.dart'; -import 'package:island/widgets/activitypub/actor_profile.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; -import 'package:island/widgets/response.dart'; -import 'package:island/widgets/activity_heatmap.dart'; +import 'package:island/accounts/accounts_widgets/account/account_picker.dart'; +import 'package:island/accounts/accounts_widgets/activitypub/actor_profile.dart'; +import 'package:island/core/models/activitypub.dart'; +import 'package:island/creators/creators/publishers_form.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/posts/posts_models/heatmap.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/core/utils/text.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; +import 'package:island/shared/widgets/response.dart'; +import 'package:island/posts/activity_heatmap.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/creators/hub.g.dart b/lib/creators/creators/hub.g.dart similarity index 100% rename from lib/screens/creators/hub.g.dart rename to lib/creators/creators/hub.g.dart diff --git a/lib/screens/creators/poll/poll_list.dart b/lib/creators/creators/poll/poll_list.dart similarity index 93% rename from lib/screens/creators/poll/poll_list.dart rename to lib/creators/creators/poll/poll_list.dart index 35e04256..a0bef841 100644 --- a/lib/screens/creators/poll/poll_list.dart +++ b/lib/creators/creators/poll/poll_list.dart @@ -2,17 +2,17 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/poll.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/screens/poll/poll_editor.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; -import 'package:island/widgets/poll/poll_feedback.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/polls/poll/poll_editor.dart'; +import 'package:island/polls/polls_widgets/poll/poll_feedback.dart'; +import 'package:island/posts/posts_models/poll.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; import 'package:styled_widget/styled_widget.dart'; part 'poll_list.g.dart'; diff --git a/lib/screens/creators/poll/poll_list.g.dart b/lib/creators/creators/poll/poll_list.g.dart similarity index 100% rename from lib/screens/creators/poll/poll_list.g.dart rename to lib/creators/creators/poll/poll_list.g.dart diff --git a/lib/screens/creators/posts/post_manage_list.dart b/lib/creators/creators/posts/post_manage_list.dart similarity index 84% rename from lib/screens/creators/posts/post_manage_list.dart rename to lib/creators/creators/posts/post_manage_list.dart index 806cbe74..670629f9 100644 --- a/lib/screens/creators/posts/post_manage_list.dart +++ b/lib/creators/creators/posts/post_manage_list.dart @@ -2,9 +2,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/post/post_list.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/post/post_list.dart'; +import 'package:island/posts/post/post_list.dart'; +import 'package:island/posts/posts_widgets/post/post_list.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; class CreatorPostListScreen extends HookConsumerWidget { final String pubName; diff --git a/lib/screens/creators/publishers_form.dart b/lib/creators/creators/publishers_form.dart similarity index 95% rename from lib/screens/creators/publishers_form.dart rename to lib/creators/creators/publishers_form.dart index 7656a9bd..174fc1cf 100644 --- a/lib/screens/creators/publishers_form.dart +++ b/lib/creators/creators/publishers_form.dart @@ -7,17 +7,17 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/screens/realm/realms.dart'; -import 'package:island/services/file.dart'; -import 'package:island/services/file_uploader.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/core/services/image.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/realms/realm/realms.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/creators/publishers_form.g.dart b/lib/creators/creators/publishers_form.g.dart similarity index 100% rename from lib/screens/creators/publishers_form.g.dart rename to lib/creators/creators/publishers_form.g.dart diff --git a/lib/screens/creators/sites/site_detail.dart b/lib/creators/creators/sites/site_detail.dart similarity index 61% rename from lib/screens/creators/sites/site_detail.dart rename to lib/creators/creators/sites/site_detail.dart index a694efb1..f35c9a60 100644 --- a/lib/screens/creators/sites/site_detail.dart +++ b/lib/creators/creators/sites/site_detail.dart @@ -2,21 +2,21 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/site_pages.dart'; -import 'package:island/widgets/sites/page_form.dart'; -import 'package:island/widgets/sites/site_action_menu.dart'; -import 'package:island/widgets/sites/site_detail_content.dart'; -import 'package:island/widgets/sites/site_info_card.dart'; -import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/sites/site_pages.dart'; +import 'package:island/sites/sites_widgets/file_management_action_section.dart'; +import 'package:island/sites/sites_widgets/file_management_section.dart'; +import 'package:island/sites/sites_widgets/page_form.dart'; +import 'package:island/sites/sites_widgets/pages_section.dart'; +import 'package:island/sites/sites_widgets/site_action_menu.dart'; +import 'package:island/sites/sites_widgets/site_detail_content.dart'; +import 'package:island/sites/sites_widgets/site_info_card.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:island/widgets/sites/pages_section.dart'; -import 'package:island/widgets/sites/file_management_section.dart'; -import 'package:island/widgets/sites/file_management_action_section.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; import 'package:styled_widget/styled_widget.dart'; part 'site_detail.g.dart'; @@ -67,10 +67,9 @@ class PublicationSiteDetailScreen extends HookConsumerWidget { data: (site) { if (isWideScreen(context)) { return ExtendedRefreshIndicator( - onRefresh: - () async => ref.invalidate( - publicationSiteDetailProvider(pubName, site.slug), - ), + onRefresh: () async => ref.invalidate( + publicationSiteDetailProvider(pubName, site.slug), + ), child: Row( spacing: 8, crossAxisAlignment: CrossAxisAlignment.start, @@ -114,46 +113,43 @@ class PublicationSiteDetailScreen extends HookConsumerWidget { return SiteDetailContent(site: site, pubName: pubName); } }, - error: - (error, stack) => Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'failedToLoadSite'.tr(), - style: Theme.of(context).textTheme.headlineSmall, - ), - const Gap(16), - Text(error.toString()), - const Gap(24), - ElevatedButton( - onPressed: - () => ref.invalidate( - publicationSiteDetailProvider(pubName, siteSlug), - ), - child: Text('retry'.tr()), - ), - ], + error: (error, stack) => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'failedToLoadSite'.tr(), + style: Theme.of(context).textTheme.headlineSmall, ), - ), + const Gap(16), + Text(error.toString()), + const Gap(24), + ElevatedButton( + onPressed: () => ref.invalidate( + publicationSiteDetailProvider(pubName, siteSlug), + ), + child: Text('retry'.tr()), + ), + ], + ), + ), loading: () => const Center(child: CircularProgressIndicator()), ), floatingActionButton: siteAsync.maybeWhen( - data: - (site) => FloatingActionButton( - onPressed: () { - // Create new page - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) => PageForm(site: site, pubName: pubName), - ).then((_) { - // Refresh pages after creation - ref.invalidate(sitePagesProvider(pubName, site.slug)); - }); - }, - child: const Icon(Symbols.add), - ), + data: (site) => FloatingActionButton( + onPressed: () { + // Create new page + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => PageForm(site: site, pubName: pubName), + ).then((_) { + // Refresh pages after creation + ref.invalidate(sitePagesProvider(pubName, site.slug)); + }); + }, + child: const Icon(Symbols.add), + ), orElse: () => null, ), ); diff --git a/lib/screens/creators/sites/site_detail.g.dart b/lib/creators/creators/sites/site_detail.g.dart similarity index 100% rename from lib/screens/creators/sites/site_detail.g.dart rename to lib/creators/creators/sites/site_detail.g.dart diff --git a/lib/screens/creators/sites/site_edit.dart b/lib/creators/creators/sites/site_edit.dart similarity index 96% rename from lib/screens/creators/sites/site_edit.dart rename to lib/creators/creators/sites/site_edit.dart index 0a383b15..1266f603 100644 --- a/lib/screens/creators/sites/site_edit.dart +++ b/lib/creators/creators/sites/site_edit.dart @@ -2,14 +2,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/creators/sites/site_detail.dart'; -import 'package:island/screens/creators/sites/site_list.dart'; -import 'package:island/screens/creators/sites/widgets/site_config_form.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/creators/creators/sites/site_detail.dart'; +import 'package:island/creators/creators/sites/site_list.dart'; +import 'package:island/creators/creators/sites/widgets/site_config_form.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/creators/sites/site_list.dart b/lib/creators/creators/sites/site_list.dart similarity index 94% rename from lib/screens/creators/sites/site_list.dart rename to lib/creators/creators/sites/site_list.dart index 92e86535..ec026a43 100644 --- a/lib/screens/creators/sites/site_list.dart +++ b/lib/creators/creators/sites/site_list.dart @@ -4,13 +4,13 @@ import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/screens/creators/sites/site_edit.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/creators/creators/sites/site_edit.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/core/network.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/creators/sites/widgets/site_config_form.dart b/lib/creators/creators/sites/widgets/site_config_form.dart similarity index 99% rename from lib/screens/creators/sites/widgets/site_config_form.dart rename to lib/creators/creators/sites/widgets/site_config_form.dart index 95c093e3..866ee08a 100644 --- a/lib/screens/creators/sites/widgets/site_config_form.dart +++ b/lib/creators/creators/sites/widgets/site_config_form.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:island/models/publication_site.dart'; +import 'package:island/creators/publication_site.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/creators/stickers/pack_detail.dart b/lib/creators/creators/stickers/pack_detail.dart similarity index 96% rename from lib/screens/creators/stickers/pack_detail.dart rename to lib/creators/creators/stickers/pack_detail.dart index 6990b571..e3018827 100644 --- a/lib/screens/creators/stickers/pack_detail.dart +++ b/lib/creators/creators/stickers/pack_detail.dart @@ -8,14 +8,14 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:gap/gap.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/sticker.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/creators/stickers/stickers.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_file_picker.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/creators/creators/stickers/stickers.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/stickers/stickers_models/sticker.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/cloud_file_picker.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/creators/stickers/pack_detail.freezed.dart b/lib/creators/creators/stickers/pack_detail.freezed.dart similarity index 100% rename from lib/screens/creators/stickers/pack_detail.freezed.dart rename to lib/creators/creators/stickers/pack_detail.freezed.dart diff --git a/lib/screens/creators/stickers/pack_detail.g.dart b/lib/creators/creators/stickers/pack_detail.g.dart similarity index 100% rename from lib/screens/creators/stickers/pack_detail.g.dart rename to lib/creators/creators/stickers/pack_detail.g.dart diff --git a/lib/screens/creators/stickers/stickers.dart b/lib/creators/creators/stickers/stickers.dart similarity index 94% rename from lib/screens/creators/stickers/stickers.dart rename to lib/creators/creators/stickers/stickers.dart index 5704aa51..1d5b8e80 100644 --- a/lib/screens/creators/stickers/stickers.dart +++ b/lib/creators/creators/stickers/stickers.dart @@ -4,18 +4,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/sticker.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_file_picker.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/screens/creators/stickers/pack_detail.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/creators/creators/stickers/pack_detail.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/stickers/stickers_models/sticker.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/core/widgets/content/cloud_file_picker.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/creators/stickers/stickers.g.dart b/lib/creators/creators/stickers/stickers.g.dart similarity index 100% rename from lib/screens/creators/stickers/stickers.g.dart rename to lib/creators/creators/stickers/stickers.g.dart diff --git a/lib/screens/creators/webfeed/webfeed_edit.dart b/lib/creators/creators/webfeed/webfeed_edit.dart similarity index 97% rename from lib/screens/creators/webfeed/webfeed_edit.dart rename to lib/creators/creators/webfeed/webfeed_edit.dart index 6c0767a1..dd3dffef 100644 --- a/lib/screens/creators/webfeed/webfeed_edit.dart +++ b/lib/creators/creators/webfeed/webfeed_edit.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/webfeed.dart'; -import 'package:island/pods/webfeed.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/discovery/discovery_models/webfeed.dart'; +import 'package:island/discovery/webfeed.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/creators/webfeed/webfeed_list.dart b/lib/creators/creators/webfeed/webfeed_list.dart similarity index 76% rename from lib/screens/creators/webfeed/webfeed_list.dart rename to lib/creators/creators/webfeed/webfeed_list.dart index fbc66f2d..b70ea46d 100644 --- a/lib/screens/creators/webfeed/webfeed_list.dart +++ b/lib/creators/creators/webfeed/webfeed_list.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:island/pods/webfeed.dart'; -import 'package:island/screens/creators/webfeed/webfeed_edit.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/empty_state.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; +import 'package:island/creators/creators/webfeed/webfeed_edit.dart'; +import 'package:island/discovery/webfeed.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/empty_state.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -27,11 +27,10 @@ class WebFeedListScreen extends ConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => SheetScaffold( - titleText: 'New Web Feed', - child: WebfeedForm(pubName: pubName, feedId: null), - ), + builder: (context) => SheetScaffold( + titleText: 'New Web Feed', + child: WebfeedForm(pubName: pubName, feedId: null), + ), ); }, ), @@ -79,14 +78,13 @@ class WebFeedListScreen extends ConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => SheetScaffold( - titleText: 'Edit Web Feed', - child: WebfeedForm( - pubName: pubName, - feedId: feed.id, - ), - ), + builder: (context) => SheetScaffold( + titleText: 'Edit Web Feed', + child: WebfeedForm( + pubName: pubName, + feedId: feed.id, + ), + ), ); }, ), diff --git a/lib/models/publication_site.dart b/lib/creators/publication_site.dart similarity index 100% rename from lib/models/publication_site.dart rename to lib/creators/publication_site.dart diff --git a/lib/models/publication_site.freezed.dart b/lib/creators/publication_site.freezed.dart similarity index 100% rename from lib/models/publication_site.freezed.dart rename to lib/creators/publication_site.freezed.dart diff --git a/lib/models/publication_site.g.dart b/lib/creators/publication_site.g.dart similarity index 100% rename from lib/models/publication_site.g.dart rename to lib/creators/publication_site.g.dart diff --git a/lib/database/database.native.dart b/lib/data/database.native.dart similarity index 90% rename from lib/database/database.native.dart rename to lib/data/database.native.dart index 42e299c8..88aa3b69 100644 --- a/lib/database/database.native.dart +++ b/lib/data/database.native.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; -import 'package:island/database/drift_db.dart'; +import 'package:island/data/drift_db.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; diff --git a/lib/database/database.web.dart b/lib/data/database.web.dart similarity index 93% rename from lib/database/database.web.dart rename to lib/data/database.web.dart index c9ca788f..9f637b3a 100644 --- a/lib/database/database.web.dart +++ b/lib/data/database.web.dart @@ -1,6 +1,6 @@ import 'package:drift/drift.dart'; import 'package:drift/wasm.dart'; -import 'package:island/database/drift_db.dart'; +import 'package:island/data/drift_db.dart'; import 'package:island/talker.dart'; AppDatabase constructDb() { diff --git a/lib/database/draft.dart b/lib/data/draft.dart similarity index 100% rename from lib/database/draft.dart rename to lib/data/draft.dart diff --git a/lib/database/drift_db.dart b/lib/data/drift_db.dart similarity index 98% rename from lib/database/drift_db.dart rename to lib/data/drift_db.dart index 1b57db3e..665693f7 100644 --- a/lib/database/drift_db.dart +++ b/lib/data/drift_db.dart @@ -1,11 +1,11 @@ import 'dart:convert'; import 'package:drift/drift.dart'; -import 'package:island/database/message.dart'; -import 'package:island/database/draft.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/models/post.dart'; -import 'package:island/models/realm.dart'; +import 'package:island/data/draft.dart'; +import 'package:island/data/message.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/realms/realms_models/realm.dart'; part 'drift_db.g.dart'; diff --git a/lib/database/drift_db.g.dart b/lib/data/drift_db.g.dart similarity index 100% rename from lib/database/drift_db.g.dart rename to lib/data/drift_db.g.dart diff --git a/lib/database/drift_db.steps.dart b/lib/data/drift_db.steps.dart similarity index 100% rename from lib/database/drift_db.steps.dart rename to lib/data/drift_db.steps.dart diff --git a/lib/database/message.dart b/lib/data/message.dart similarity index 98% rename from lib/database/message.dart rename to lib/data/message.dart index 27cb63ef..b7907e0a 100644 --- a/lib/database/message.dart +++ b/lib/data/message.dart @@ -1,8 +1,8 @@ import 'dart:convert'; import 'package:drift/drift.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/models/file.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/drive/drive_models/file.dart'; class MapConverter extends TypeConverter, String> { const MapConverter(); diff --git a/lib/screens/developers/app_detail.dart b/lib/developers/developers/app_detail.dart similarity index 93% rename from lib/screens/developers/app_detail.dart rename to lib/developers/developers/app_detail.dart index 861b909f..b641ab8b 100644 --- a/lib/screens/developers/app_detail.dart +++ b/lib/developers/developers/app_detail.dart @@ -4,12 +4,12 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/custom_app.dart'; -import 'package:island/screens/developers/app_secrets.dart'; -import 'package:island/screens/developers/apps.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/developers/developers/app_secrets.dart'; +import 'package:island/developers/developers/apps.dart'; +import 'package:island/developers/developers_models/custom_app.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/developers/app_secrets.dart b/lib/developers/developers/app_secrets.dart similarity index 96% rename from lib/screens/developers/app_secrets.dart rename to lib/developers/developers/app_secrets.dart index d37cdeb0..85ce43f2 100644 --- a/lib/screens/developers/app_secrets.dart +++ b/lib/developers/developers/app_secrets.dart @@ -4,12 +4,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/custom_app_secret.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/developers/developers_models/custom_app_secret.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/lib/screens/developers/app_secrets.g.dart b/lib/developers/developers/app_secrets.g.dart similarity index 100% rename from lib/screens/developers/app_secrets.g.dart rename to lib/developers/developers/app_secrets.g.dart diff --git a/lib/screens/developers/apps.dart b/lib/developers/developers/apps.dart similarity index 94% rename from lib/screens/developers/apps.dart rename to lib/developers/developers/apps.dart index 358e50ad..d0986da3 100644 --- a/lib/screens/developers/apps.dart +++ b/lib/developers/developers/apps.dart @@ -4,15 +4,15 @@ import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/custom_app.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/developers/edit_app.dart'; -import 'package:island/screens/developers/new_app.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/developers/developers/edit_app.dart'; +import 'package:island/developers/developers/new_app.dart'; +import 'package:island/developers/developers_models/custom_app.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/developers/apps.g.dart b/lib/developers/developers/apps.g.dart similarity index 100% rename from lib/screens/developers/apps.g.dart rename to lib/developers/developers/apps.g.dart diff --git a/lib/screens/developers/bot_detail.dart b/lib/developers/developers/bot_detail.dart similarity index 93% rename from lib/screens/developers/bot_detail.dart rename to lib/developers/developers/bot_detail.dart index f4ed91ab..f2577e7c 100644 --- a/lib/screens/developers/bot_detail.dart +++ b/lib/developers/developers/bot_detail.dart @@ -2,12 +2,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/bot.dart'; -import 'package:island/screens/developers/bot_keys.dart'; -import 'package:island/screens/developers/edit_bot.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/developers/developers/bot_keys.dart'; +import 'package:island/developers/developers/edit_bot.dart'; +import 'package:island/developers/developers_models/bot.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:go_router/go_router.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/developers/bot_keys.dart b/lib/developers/developers/bot_keys.dart similarity index 96% rename from lib/screens/developers/bot_keys.dart rename to lib/developers/developers/bot_keys.dart index 4ea8d864..77d901ae 100644 --- a/lib/screens/developers/bot_keys.dart +++ b/lib/developers/developers/bot_keys.dart @@ -4,12 +4,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/bot_key.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/developers/developers_models/bot_key.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/lib/screens/developers/bot_keys.g.dart b/lib/developers/developers/bot_keys.g.dart similarity index 100% rename from lib/screens/developers/bot_keys.g.dart rename to lib/developers/developers/bot_keys.g.dart diff --git a/lib/screens/developers/bots.dart b/lib/developers/developers/bots.dart similarity index 94% rename from lib/screens/developers/bots.dart rename to lib/developers/developers/bots.dart index 84ef7f4f..c2b8a9f8 100644 --- a/lib/screens/developers/bots.dart +++ b/lib/developers/developers/bots.dart @@ -3,17 +3,17 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/bot.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/developers/edit_bot.dart'; -import 'package:island/screens/developers/new_bot.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/developers/developers/edit_bot.dart'; +import 'package:island/developers/developers/new_bot.dart'; +import 'package:island/developers/developers_models/bot.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; import 'package:styled_widget/styled_widget.dart'; part 'bots.g.dart'; diff --git a/lib/screens/developers/bots.g.dart b/lib/developers/developers/bots.g.dart similarity index 100% rename from lib/screens/developers/bots.g.dart rename to lib/developers/developers/bots.g.dart diff --git a/lib/screens/developers/edit_app.dart b/lib/developers/developers/edit_app.dart similarity index 97% rename from lib/screens/developers/edit_app.dart rename to lib/developers/developers/edit_app.dart index d2ade277..87ca790a 100644 --- a/lib/screens/developers/edit_app.dart +++ b/lib/developers/developers/edit_app.dart @@ -4,20 +4,20 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:island/models/custom_app.dart'; -import 'package:island/models/file.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/developers/apps.dart'; -import 'package:island/services/file.dart'; -import 'package:island/services/file_uploader.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/core/services/image.dart'; +import 'package:island/developers/developers/apps.dart'; +import 'package:island/developers/developers_models/custom_app.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/core/network.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/core/widgets/content/sheet.dart'; part 'edit_app.g.dart'; diff --git a/lib/screens/developers/edit_app.g.dart b/lib/developers/developers/edit_app.g.dart similarity index 100% rename from lib/screens/developers/edit_app.g.dart rename to lib/developers/developers/edit_app.g.dart diff --git a/lib/screens/developers/edit_bot.dart b/lib/developers/developers/edit_bot.dart similarity index 97% rename from lib/screens/developers/edit_bot.dart rename to lib/developers/developers/edit_bot.dart index 074eea10..97eb3484 100644 --- a/lib/screens/developers/edit_bot.dart +++ b/lib/developers/developers/edit_bot.dart @@ -5,15 +5,15 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:island/models/bot.dart'; -import 'package:island/models/file.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/services/file.dart'; -import 'package:island/services/file_uploader.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/core/services/image.dart'; +import 'package:island/developers/developers_models/bot.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/core/network.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/developers/edit_bot.g.dart b/lib/developers/developers/edit_bot.g.dart similarity index 100% rename from lib/screens/developers/edit_bot.g.dart rename to lib/developers/developers/edit_bot.g.dart diff --git a/lib/screens/developers/edit_project.dart b/lib/developers/developers/edit_project.dart similarity index 51% rename from lib/screens/developers/edit_project.dart rename to lib/developers/developers/edit_project.dart index 875c17dc..127bf340 100644 --- a/lib/screens/developers/edit_project.dart +++ b/lib/developers/developers/edit_project.dart @@ -3,11 +3,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/dev_project.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/developers/hub.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/developers/developers/hub.dart'; +import 'package:island/developers/developers_models/dev_project.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -29,8 +29,9 @@ class EditProjectScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final isNew = id == null; - final projectData = - isNew ? null : ref.watch(devProjectProvider(publisherName, id!)); + final projectData = isNew + ? null + : ref.watch(devProjectProvider(publisherName, id!)); final formKey = useMemoized(() => GlobalKey()); final submitting = useState(false); @@ -76,55 +77,53 @@ class EditProjectScreen extends HookConsumerWidget { appBar: AppBar( title: Text(isNew ? 'createProject'.tr() : 'editProject'.tr()), ), - body: - projectData == null && !isNew - ? const Center(child: CircularProgressIndicator()) - : projectData?.hasError == true && !isNew - ? ResponseErrorWidget( - error: projectData!.error, - onRetry: - () => - ref.invalidate(devProjectProvider(publisherName, id!)), - ) - : SingleChildScrollView( - child: Form( - key: formKey, - child: Column( - children: [ - TextFormField( - controller: nameController, - decoration: InputDecoration(labelText: 'name'.tr()), + body: projectData == null && !isNew + ? const Center(child: CircularProgressIndicator()) + : projectData?.hasError == true && !isNew + ? ResponseErrorWidget( + error: projectData!.error, + onRetry: () => + ref.invalidate(devProjectProvider(publisherName, id!)), + ) + : SingleChildScrollView( + child: Form( + key: formKey, + child: Column( + children: [ + TextFormField( + controller: nameController, + decoration: InputDecoration(labelText: 'name'.tr()), + ), + const SizedBox(height: 16), + TextFormField( + controller: slugController, + decoration: InputDecoration( + labelText: 'slug'.tr(), + helperText: 'slugHint'.tr(), ), - const SizedBox(height: 16), - TextFormField( - controller: slugController, - decoration: InputDecoration( - labelText: 'slug'.tr(), - helperText: 'slugHint'.tr(), - ), + ), + const SizedBox(height: 16), + TextFormField( + controller: descriptionController, + decoration: InputDecoration( + labelText: 'description'.tr(), + alignLabelWithHint: true, ), - const SizedBox(height: 16), - TextFormField( - controller: descriptionController, - decoration: InputDecoration( - labelText: 'description'.tr(), - alignLabelWithHint: true, - ), - maxLines: 3, + maxLines: 3, + ), + const SizedBox(height: 16), + Align( + alignment: Alignment.centerRight, + child: TextButton.icon( + onPressed: submitting.value ? null : performAction, + label: Text('saveChanges'.tr()), + icon: const Icon(Symbols.save), ), - const SizedBox(height: 16), - Align( - alignment: Alignment.centerRight, - child: TextButton.icon( - onPressed: submitting.value ? null : performAction, - label: Text('saveChanges'.tr()), - icon: const Icon(Symbols.save), - ), - ), - ], - ).padding(all: 24), - ), + ), + ], + ).padding(all: 24), ), + ), ); } } diff --git a/lib/screens/developers/edit_project.g.dart b/lib/developers/developers/edit_project.g.dart similarity index 100% rename from lib/screens/developers/edit_project.g.dart rename to lib/developers/developers/edit_project.g.dart diff --git a/lib/screens/developers/hub.dart b/lib/developers/developers/hub.dart similarity index 97% rename from lib/screens/developers/hub.dart rename to lib/developers/developers/hub.dart index e1403c5b..c6130d87 100644 --- a/lib/screens/developers/hub.dart +++ b/lib/developers/developers/hub.dart @@ -4,18 +4,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/dev_project.dart'; -import 'package:island/models/developer.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/creators/publishers_form.dart'; -import 'package:island/screens/developers/project_detail_view.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/creators/creators/publishers_form.dart'; +import 'package:island/developers/developers/project_detail_view.dart'; +import 'package:island/developers/developers_models/dev_project.dart'; +import 'package:island/developers/developers_models/developer.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/developers/hub.g.dart b/lib/developers/developers/hub.g.dart similarity index 100% rename from lib/screens/developers/hub.g.dart rename to lib/developers/developers/hub.g.dart diff --git a/lib/screens/developers/new_app.dart b/lib/developers/developers/new_app.dart similarity index 88% rename from lib/screens/developers/new_app.dart rename to lib/developers/developers/new_app.dart index 8f4dcaa4..a73daba6 100644 --- a/lib/screens/developers/new_app.dart +++ b/lib/developers/developers/new_app.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:island/screens/developers/edit_app.dart'; +import 'package:island/developers/developers/edit_app.dart'; class NewCustomAppScreen extends StatelessWidget { final String publisherName; diff --git a/lib/screens/developers/new_bot.dart b/lib/developers/developers/new_bot.dart similarity index 88% rename from lib/screens/developers/new_bot.dart rename to lib/developers/developers/new_bot.dart index 0cc9c2f8..7630d8ad 100644 --- a/lib/screens/developers/new_bot.dart +++ b/lib/developers/developers/new_bot.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:island/screens/developers/edit_bot.dart'; +import 'package:island/developers/developers/edit_bot.dart'; class NewBotScreen extends StatelessWidget { final String publisherName; diff --git a/lib/screens/developers/new_project.dart b/lib/developers/developers/new_project.dart similarity index 82% rename from lib/screens/developers/new_project.dart rename to lib/developers/developers/new_project.dart index 00884059..146a11db 100644 --- a/lib/screens/developers/new_project.dart +++ b/lib/developers/developers/new_project.dart @@ -1,6 +1,5 @@ - import 'package:flutter/material.dart'; -import 'package:island/screens/developers/edit_project.dart'; +import 'package:island/developers/developers/edit_project.dart'; class NewProjectScreen extends StatelessWidget { final String publisherName; diff --git a/lib/screens/developers/project_detail_view.dart b/lib/developers/developers/project_detail_view.dart similarity index 95% rename from lib/screens/developers/project_detail_view.dart rename to lib/developers/developers/project_detail_view.dart index 3bec8a8a..30a9d9ec 100644 --- a/lib/screens/developers/project_detail_view.dart +++ b/lib/developers/developers/project_detail_view.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/dev_project.dart'; -import 'package:island/screens/developers/apps.dart'; -import 'package:island/screens/developers/bots.dart'; -import 'package:island/services/responsive.dart'; +import 'package:island/developers/developers/apps.dart'; +import 'package:island/developers/developers/bots.dart'; +import 'package:island/developers/developers_models/dev_project.dart'; +import 'package:island/core/services/responsive.dart'; import 'package:styled_widget/styled_widget.dart'; class ProjectDetailView extends HookConsumerWidget { diff --git a/lib/models/bot.dart b/lib/developers/developers_models/bot.dart similarity index 92% rename from lib/models/bot.dart rename to lib/developers/developers_models/bot.dart index 858634f9..219341d1 100644 --- a/lib/models/bot.dart +++ b/lib/developers/developers_models/bot.dart @@ -1,6 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/developer.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/developers/developers_models/developer.dart'; part 'bot.freezed.dart'; part 'bot.g.dart'; diff --git a/lib/models/bot.freezed.dart b/lib/developers/developers_models/bot.freezed.dart similarity index 100% rename from lib/models/bot.freezed.dart rename to lib/developers/developers_models/bot.freezed.dart diff --git a/lib/models/bot.g.dart b/lib/developers/developers_models/bot.g.dart similarity index 100% rename from lib/models/bot.g.dart rename to lib/developers/developers_models/bot.g.dart diff --git a/lib/models/bot_key.dart b/lib/developers/developers_models/bot_key.dart similarity index 100% rename from lib/models/bot_key.dart rename to lib/developers/developers_models/bot_key.dart diff --git a/lib/models/bot_key.freezed.dart b/lib/developers/developers_models/bot_key.freezed.dart similarity index 100% rename from lib/models/bot_key.freezed.dart rename to lib/developers/developers_models/bot_key.freezed.dart diff --git a/lib/models/bot_key.g.dart b/lib/developers/developers_models/bot_key.g.dart similarity index 100% rename from lib/models/bot_key.g.dart rename to lib/developers/developers_models/bot_key.g.dart diff --git a/lib/models/custom_app.dart b/lib/developers/developers_models/custom_app.dart similarity index 94% rename from lib/models/custom_app.dart rename to lib/developers/developers_models/custom_app.dart index 141f0826..27d6324a 100644 --- a/lib/models/custom_app.dart +++ b/lib/developers/developers_models/custom_app.dart @@ -1,6 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/account.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/accounts/accounts_models/account.dart'; part 'custom_app.freezed.dart'; part 'custom_app.g.dart'; diff --git a/lib/models/custom_app.freezed.dart b/lib/developers/developers_models/custom_app.freezed.dart similarity index 100% rename from lib/models/custom_app.freezed.dart rename to lib/developers/developers_models/custom_app.freezed.dart diff --git a/lib/models/custom_app.g.dart b/lib/developers/developers_models/custom_app.g.dart similarity index 100% rename from lib/models/custom_app.g.dart rename to lib/developers/developers_models/custom_app.g.dart diff --git a/lib/models/custom_app_secret.dart b/lib/developers/developers_models/custom_app_secret.dart similarity index 100% rename from lib/models/custom_app_secret.dart rename to lib/developers/developers_models/custom_app_secret.dart diff --git a/lib/models/custom_app_secret.freezed.dart b/lib/developers/developers_models/custom_app_secret.freezed.dart similarity index 100% rename from lib/models/custom_app_secret.freezed.dart rename to lib/developers/developers_models/custom_app_secret.freezed.dart diff --git a/lib/models/custom_app_secret.g.dart b/lib/developers/developers_models/custom_app_secret.g.dart similarity index 100% rename from lib/models/custom_app_secret.g.dart rename to lib/developers/developers_models/custom_app_secret.g.dart diff --git a/lib/models/dev_project.dart b/lib/developers/developers_models/dev_project.dart similarity index 100% rename from lib/models/dev_project.dart rename to lib/developers/developers_models/dev_project.dart diff --git a/lib/models/developer.dart b/lib/developers/developers_models/developer.dart similarity index 91% rename from lib/models/developer.dart rename to lib/developers/developers_models/developer.dart index 7f6f54e8..a91aad76 100644 --- a/lib/models/developer.dart +++ b/lib/developers/developers_models/developer.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/publisher.dart'; +import 'package:island/posts/posts_models/publisher.dart'; part 'developer.freezed.dart'; part 'developer.g.dart'; diff --git a/lib/models/developer.freezed.dart b/lib/developers/developers_models/developer.freezed.dart similarity index 100% rename from lib/models/developer.freezed.dart rename to lib/developers/developers_models/developer.freezed.dart diff --git a/lib/models/developer.g.dart b/lib/developers/developers_models/developer.g.dart similarity index 100% rename from lib/models/developer.g.dart rename to lib/developers/developers_models/developer.g.dart diff --git a/lib/screens/discovery/article_detail.dart b/lib/discovery/discovery/article_detail.dart similarity index 78% rename from lib/screens/discovery/article_detail.dart rename to lib/discovery/discovery/article_detail.dart index 862d283c..172ccad1 100644 --- a/lib/screens/discovery/article_detail.dart +++ b/lib/discovery/discovery/article_detail.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/widgets/content/markdown.dart'; +import 'package:island/core/widgets/content/markdown.dart'; +import 'package:island/discovery/discovery/article_pod.dart'; import 'package:url_launcher/url_launcher_string.dart'; -import 'package:island/models/webfeed.dart'; -import 'package:island/pods/article_detail.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/loading_indicator.dart'; +import 'package:island/discovery/discovery_models/webfeed.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/loading_indicator.dart'; import 'package:html2md/html2md.dart' as html2md; class ArticleDetailScreen extends ConsumerWidget { @@ -22,18 +22,16 @@ class ArticleDetailScreen extends ConsumerWidget { return AppScaffold( isNoBackground: false, body: articleAsync.when( - data: - (article) => AppScaffold( - appBar: AppBar( - leading: const BackButton(), - title: Text(article.title), - ), - body: _ArticleDetailContent(article: article), - ), + data: (article) => AppScaffold( + appBar: AppBar( + leading: const BackButton(), + title: Text(article.title), + ), + body: _ArticleDetailContent(article: article), + ), loading: () => const Center(child: LoadingIndicator()), - error: - (error, stackTrace) => - Center(child: Text('Failed to load article: $error')), + error: (error, stackTrace) => + Center(child: Text('Failed to load article: $error')), ), ); } @@ -91,11 +89,10 @@ class _ArticleDetailContent extends HookConsumerWidget { Text(article.preview!.description!), const Gap(24), FilledButton( - onPressed: - () => launchUrlString( - article.url, - mode: LaunchMode.externalApplication, - ), + onPressed: () => launchUrlString( + article.url, + mode: LaunchMode.externalApplication, + ), child: const Text('Read Full Article'), ), Gap(MediaQuery.of(context).padding.bottom), diff --git a/lib/pods/article_detail.dart b/lib/discovery/discovery/article_pod.dart similarity index 84% rename from lib/pods/article_detail.dart rename to lib/discovery/discovery/article_pod.dart index 67d557d2..5ebce907 100644 --- a/lib/pods/article_detail.dart +++ b/lib/discovery/discovery/article_pod.dart @@ -1,7 +1,7 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:dio/dio.dart'; -import 'package:island/models/webfeed.dart'; -import 'package:island/pods/network.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/core/network.dart'; +import 'package:island/discovery/discovery_models/webfeed.dart'; /// Provider that fetches a single article by its ID final articleDetailProvider = FutureProvider.autoDispose diff --git a/lib/screens/discovery/articles.dart b/lib/discovery/discovery/articles.dart similarity index 94% rename from lib/screens/discovery/articles.dart rename to lib/discovery/discovery/articles.dart index 468bdbe4..33a70b8c 100644 --- a/lib/screens/discovery/articles.dart +++ b/lib/discovery/discovery/articles.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/webfeed.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; -import 'package:island/widgets/web_article_card.dart'; +import 'package:island/discovery/discovery_models/webfeed.dart'; +import 'package:island/core/network.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; +import 'package:island/discovery/web_article_card.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'articles.g.dart'; diff --git a/lib/screens/discovery/articles.freezed.dart b/lib/discovery/discovery/articles.freezed.dart similarity index 100% rename from lib/screens/discovery/articles.freezed.dart rename to lib/discovery/discovery/articles.freezed.dart diff --git a/lib/screens/discovery/articles.g.dart b/lib/discovery/discovery/articles.g.dart similarity index 100% rename from lib/screens/discovery/articles.g.dart rename to lib/discovery/discovery/articles.g.dart diff --git a/lib/screens/discovery/feeds/feed_detail.dart b/lib/discovery/discovery/feeds/feed_detail.dart similarity index 94% rename from lib/screens/discovery/feeds/feed_detail.dart rename to lib/discovery/discovery/feeds/feed_detail.dart index e9406d6c..b88f699d 100644 --- a/lib/screens/discovery/feeds/feed_detail.dart +++ b/lib/discovery/discovery/feeds/feed_detail.dart @@ -2,13 +2,13 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/webfeed.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; -import 'package:island/widgets/web_article_card.dart'; +import 'package:island/discovery/discovery_models/webfeed.dart'; +import 'package:island/core/network.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; +import 'package:island/discovery/web_article_card.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/discovery/feeds/feed_detail.g.dart b/lib/discovery/discovery/feeds/feed_detail.g.dart similarity index 100% rename from lib/screens/discovery/feeds/feed_detail.g.dart rename to lib/discovery/discovery/feeds/feed_detail.g.dart diff --git a/lib/screens/discovery/feeds/feed_marketplace.dart b/lib/discovery/discovery/feeds/feed_marketplace.dart similarity index 94% rename from lib/screens/discovery/feeds/feed_marketplace.dart rename to lib/discovery/discovery/feeds/feed_marketplace.dart index 5a322c14..3b3b2ca8 100644 --- a/lib/screens/discovery/feeds/feed_marketplace.dart +++ b/lib/discovery/discovery/feeds/feed_marketplace.dart @@ -5,11 +5,11 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/webfeed.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/discovery/discovery_models/webfeed.dart'; +import 'package:island/core/network.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; final marketplaceWebFeedsNotifierProvider = AsyncNotifierProvider.autoDispose( diff --git a/lib/screens/discovery/realms.dart b/lib/discovery/discovery/realms.dart similarity index 94% rename from lib/screens/discovery/realms.dart rename to lib/discovery/discovery/realms.dart index 2c41cec2..e91f59d6 100644 --- a/lib/screens/discovery/realms.dart +++ b/lib/discovery/discovery/realms.dart @@ -3,8 +3,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/realm/realm_list.dart'; +import 'package:island/realms/realms_widgets/realm/realm_list.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; import 'dart:async'; class DiscoveryRealmsScreen extends HookConsumerWidget { diff --git a/lib/models/auto_completion.dart b/lib/discovery/discovery_models/auto_completion.dart similarity index 100% rename from lib/models/auto_completion.dart rename to lib/discovery/discovery_models/auto_completion.dart diff --git a/lib/models/auto_completion.freezed.dart b/lib/discovery/discovery_models/auto_completion.freezed.dart similarity index 100% rename from lib/models/auto_completion.freezed.dart rename to lib/discovery/discovery_models/auto_completion.freezed.dart diff --git a/lib/models/auto_completion.g.dart b/lib/discovery/discovery_models/auto_completion.g.dart similarity index 100% rename from lib/models/auto_completion.g.dart rename to lib/discovery/discovery_models/auto_completion.g.dart diff --git a/lib/models/autocomplete_response.dart b/lib/discovery/discovery_models/autocomplete_response.dart similarity index 100% rename from lib/models/autocomplete_response.dart rename to lib/discovery/discovery_models/autocomplete_response.dart diff --git a/lib/models/autocomplete_response.freezed.dart b/lib/discovery/discovery_models/autocomplete_response.freezed.dart similarity index 100% rename from lib/models/autocomplete_response.freezed.dart rename to lib/discovery/discovery_models/autocomplete_response.freezed.dart diff --git a/lib/models/autocomplete_response.g.dart b/lib/discovery/discovery_models/autocomplete_response.g.dart similarity index 100% rename from lib/models/autocomplete_response.g.dart rename to lib/discovery/discovery_models/autocomplete_response.g.dart diff --git a/lib/models/site_file.dart b/lib/discovery/discovery_models/site_file.dart similarity index 100% rename from lib/models/site_file.dart rename to lib/discovery/discovery_models/site_file.dart diff --git a/lib/models/site_file.freezed.dart b/lib/discovery/discovery_models/site_file.freezed.dart similarity index 100% rename from lib/models/site_file.freezed.dart rename to lib/discovery/discovery_models/site_file.freezed.dart diff --git a/lib/models/site_file.g.dart b/lib/discovery/discovery_models/site_file.g.dart similarity index 100% rename from lib/models/site_file.g.dart rename to lib/discovery/discovery_models/site_file.g.dart diff --git a/lib/models/webfeed.dart b/lib/discovery/discovery_models/webfeed.dart similarity index 96% rename from lib/models/webfeed.dart rename to lib/discovery/discovery_models/webfeed.dart index 81ab5503..17ea9e65 100644 --- a/lib/models/webfeed.dart +++ b/lib/discovery/discovery_models/webfeed.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/embed.dart'; +import 'package:island/posts/posts_models/embed.dart'; part 'webfeed.freezed.dart'; part 'webfeed.g.dart'; diff --git a/lib/models/webfeed.freezed.dart b/lib/discovery/discovery_models/webfeed.freezed.dart similarity index 100% rename from lib/models/webfeed.freezed.dart rename to lib/discovery/discovery_models/webfeed.freezed.dart diff --git a/lib/models/webfeed.g.dart b/lib/discovery/discovery_models/webfeed.g.dart similarity index 100% rename from lib/models/webfeed.g.dart rename to lib/discovery/discovery_models/webfeed.g.dart diff --git a/lib/services/autocomplete_service.dart b/lib/discovery/discovery_service.dart similarity index 89% rename from lib/services/autocomplete_service.dart rename to lib/discovery/discovery_service.dart index d6c833df..9a847e3f 100644 --- a/lib/services/autocomplete_service.dart +++ b/lib/discovery/discovery_service.dart @@ -1,7 +1,7 @@ import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:island/models/autocomplete_response.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/discovery/discovery_models/autocomplete_response.dart'; +import 'package:island/core/network.dart'; final autocompleteServiceProvider = Provider((ref) { final dio = ref.watch(apiClientProvider); diff --git a/lib/screens/explore.dart b/lib/discovery/explore.dart similarity index 93% rename from lib/screens/explore.dart rename to lib/discovery/explore.dart index 2d724605..5c224312 100644 --- a/lib/screens/explore.dart +++ b/lib/discovery/explore.dart @@ -4,34 +4,34 @@ import 'package:go_router/go_router.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/activity.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/models/webfeed.dart'; -import 'package:island/pods/event_calendar.dart'; -import 'package:island/pods/timeline.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/screens/auth/login_modal.dart'; -import 'package:island/screens/notification.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/models/post.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; -import 'package:island/widgets/post/compose_sheet.dart'; -import 'package:island/widgets/post/post_item.dart'; -import 'package:island/widgets/post/post_item_skeleton.dart'; -import 'package:island/widgets/post/post_list.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/models/activity.dart'; +import 'package:island/notifications/notification.dart'; +import 'package:island/posts/post/post_list.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/posts/posts_widgets/post/filters/post_subscription_filter.dart'; +import 'package:island/posts/posts_widgets/post/post_item.dart'; +import 'package:island/posts/posts_widgets/post/post_item_skeleton.dart'; +import 'package:island/posts/posts_widgets/publisher/publisher_card.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/discovery/discovery_models/webfeed.dart'; +import 'package:island/accounts/event_calendar.dart'; +import 'package:island/posts/posts_pod.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/auth/login_modal.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/realms/realms_widgets/realm/realm_card.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; +import 'package:island/posts/posts_widgets/compose_sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:island/widgets/realm/realm_card.dart'; -import 'package:island/widgets/publisher/publisher_card.dart'; -import 'package:island/widgets/web_article_card.dart'; -import 'package:island/services/event_bus.dart'; +import 'package:island/discovery/web_article_card.dart'; +import 'package:island/core/services/event_bus.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; -import 'package:island/widgets/post/filters/post_subscription_filter.dart'; -import 'package:island/pods/post/post_list.dart'; +import 'package:island/posts/posts_widgets/post/post_list.dart'; class ExploreScreen extends HookConsumerWidget { const ExploreScreen({super.key}); diff --git a/lib/screens/search.dart b/lib/discovery/search.dart similarity index 95% rename from lib/screens/search.dart rename to lib/discovery/search.dart index 141973ad..1d35bd8b 100644 --- a/lib/screens/search.dart +++ b/lib/discovery/search.dart @@ -4,23 +4,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:gap/gap.dart'; -import 'package:island/models/activitypub.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/post/post_list.dart'; -import 'package:island/services/activitypub_service.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/activitypub/actor_list_item.dart'; -import 'package:island/widgets/account/account_picker.dart'; -import 'package:island/widgets/account/account_name.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; -import 'package:island/widgets/post/post_item.dart'; -import 'package:island/widgets/post/post_item_skeleton.dart'; -import 'package:island/widgets/post/filters/post_filter.dart'; -import 'package:island/widgets/realm/realm_list.dart'; +import 'package:island/accounts/accounts_widgets/account/account_name.dart'; +import 'package:island/accounts/accounts_widgets/account/account_picker.dart'; +import 'package:island/accounts/accounts_widgets/activitypub/actor_list_item.dart'; +import 'package:island/core/models/activitypub.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/posts/post/post_list.dart'; +import 'package:island/posts/posts_widgets/post/filters/post_filter.dart'; +import 'package:island/posts/posts_widgets/post/post_item.dart'; +import 'package:island/posts/posts_widgets/post/post_item_skeleton.dart'; +import 'package:island/core/services/activitypub_service.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/realms/realms_widgets/realm/realm_list.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/web_article_card.dart b/lib/discovery/web_article_card.dart similarity index 98% rename from lib/widgets/web_article_card.dart rename to lib/discovery/web_article_card.dart index 0cf8bb9a..e3d93a76 100644 --- a/lib/widgets/web_article_card.dart +++ b/lib/discovery/web_article_card.dart @@ -1,8 +1,8 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:island/models/webfeed.dart'; -import 'package:island/services/time.dart'; +import 'package:island/discovery/discovery_models/webfeed.dart'; +import 'package:island/core/services/time.dart'; import 'package:material_symbols_icons/symbols.dart'; class WebArticleCard extends StatelessWidget { diff --git a/lib/pods/webfeed.dart b/lib/discovery/webfeed.dart similarity index 96% rename from lib/pods/webfeed.dart rename to lib/discovery/webfeed.dart index 92649752..76ff7d2f 100644 --- a/lib/pods/webfeed.dart +++ b/lib/discovery/webfeed.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:island/models/webfeed.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/discovery/discovery_models/webfeed.dart'; +import 'package:island/core/network.dart'; final webFeedListProvider = FutureProvider.autoDispose .family, String>((ref, pubName) async { diff --git a/lib/pods/drive/file_list.dart b/lib/drive/drive/file_list.dart similarity index 95% rename from lib/pods/drive/file_list.dart rename to lib/drive/drive/file_list.dart index ebccf151..d0c4cd7f 100644 --- a/lib/pods/drive/file_list.dart +++ b/lib/drive/drive/file_list.dart @@ -1,8 +1,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/file_list_item.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/drive/drive_models/file_list_item.dart'; +import 'package:island/core/network.dart'; +import 'package:island/pagination/pagination.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'file_list.g.dart'; diff --git a/lib/pods/drive/file_list.g.dart b/lib/drive/drive/file_list.g.dart similarity index 100% rename from lib/pods/drive/file_list.g.dart rename to lib/drive/drive/file_list.g.dart diff --git a/lib/pods/drive/file_pool.dart b/lib/drive/drive/file_pool.dart similarity index 81% rename from lib/pods/drive/file_pool.dart rename to lib/drive/drive/file_pool.dart index d1beeea6..e1f06e63 100644 --- a/lib/pods/drive/file_pool.dart +++ b/lib/drive/drive/file_pool.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file_pool.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/drive/drive_models/file_pool.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; final poolsProvider = FutureProvider>((ref) async { final dio = ref.watch(apiClientProvider); diff --git a/lib/pods/drive/upload_tasks.dart b/lib/drive/drive/upload_tasks.dart similarity index 79% rename from lib/pods/drive/upload_tasks.dart rename to lib/drive/drive/upload_tasks.dart index 8a1acb11..93c209d5 100644 --- a/lib/pods/drive/upload_tasks.dart +++ b/lib/drive/drive/upload_tasks.dart @@ -2,11 +2,11 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:cross_file/cross_file.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/drive_task.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/websocket.dart'; -import 'package:island/services/file_uploader.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/drive/drive_models/drive_task.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/websocket.dart'; +import 'package:island/drive/drive_service.dart'; import 'package:island/talker.dart'; final uploadTasksProvider = NotifierProvider(UploadTasksNotifier.new); @@ -73,7 +73,9 @@ class UploadTasksNotifier extends Notifier> { _handleUploadCompleted(data['task_id'], data); } else { // If no data, perhaps complete the most recent in-progress task - final inProgressTasks = state.where((task) => task.status == DriveTaskStatus.inProgress).toList(); + final inProgressTasks = state + .where((task) => task.status == DriveTaskStatus.inProgress) + .toList(); if (inProgressTasks.isNotEmpty) { final task = inProgressTasks.last; // Assume the last one _handleUploadCompleted(task.taskId, {}); @@ -91,21 +93,21 @@ class UploadTasksNotifier extends Notifier> { talker.info('[UploadTasks] Handling task.created for taskId: $taskId'); // Check if task already exists (might have been created locally) - final existingTask = - state.where((task) => task.taskId == taskId).firstOrNull; + final existingTask = state + .where((task) => task.taskId == taskId) + .firstOrNull; if (existingTask != null) { talker.info('[UploadTasks] Task already exists, updating status'); // Task already exists, just update its status to confirm server creation - state = - state.map((task) { - if (task.taskId == taskId) { - return task.copyWith( - status: DriveTaskStatus.pending, - updatedAt: DateTime.now(), - ); - } - return task; - }).toList(); + state = state.map((task) { + if (task.taskId == taskId) { + return task.copyWith( + status: DriveTaskStatus.pending, + updatedAt: DateTime.now(), + ); + } + return task; + }).toList(); return; } @@ -171,60 +173,56 @@ class UploadTasksNotifier extends Notifier> { void _handleProgressUpdate(String taskId, Map data) { final progress = data['progress'] as num? ?? 0.0; - state = - state.map((task) { - if (task.taskId == taskId) { - final uploadedBytes = (progress / 100.0 * task.fileSize).toInt(); - return task.copyWith( - statusMessage: data['status'], - uploadedBytes: uploadedBytes, - status: DriveTaskStatus.inProgress, - updatedAt: DateTime.now(), - ); - } - return task; - }).toList(); + state = state.map((task) { + if (task.taskId == taskId) { + final uploadedBytes = (progress / 100.0 * task.fileSize).toInt(); + return task.copyWith( + statusMessage: data['status'], + uploadedBytes: uploadedBytes, + status: DriveTaskStatus.inProgress, + updatedAt: DateTime.now(), + ); + } + return task; + }).toList(); } void _handleUploadCompleted(String taskId, Map data) { final results = data['results'] as Map?; - state = - state.map((task) { - if (task.taskId == taskId) { - return task.copyWith( - status: DriveTaskStatus.completed, - uploadedChunks: task.totalChunks, - uploadedBytes: task.fileSize, - // Update file information from Results if available - fileName: results?['file_name'] as String? ?? task.fileName, - fileSize: results?['file_size'] as int? ?? task.fileSize, - contentType: results?['mime_type'] as String? ?? task.contentType, - result: - results?['file_info'] != null - ? SnCloudFile.fromJson(results!['file_info']) - : null, - updatedAt: DateTime.now(), - ); - } - return task; - }).toList(); + state = state.map((task) { + if (task.taskId == taskId) { + return task.copyWith( + status: DriveTaskStatus.completed, + uploadedChunks: task.totalChunks, + uploadedBytes: task.fileSize, + // Update file information from Results if available + fileName: results?['file_name'] as String? ?? task.fileName, + fileSize: results?['file_size'] as int? ?? task.fileSize, + contentType: results?['mime_type'] as String? ?? task.contentType, + result: results?['file_info'] != null + ? SnCloudFile.fromJson(results!['file_info']) + : null, + updatedAt: DateTime.now(), + ); + } + return task; + }).toList(); } void _handleUploadFailed(String taskId, Map data) { final errorMessage = data['error_message'] as String? ?? 'Upload failed'; - state = - state.map((task) { - if (task.taskId == taskId) { - return task.copyWith( - status: DriveTaskStatus.failed, - errorMessage: errorMessage, - updatedAt: DateTime.now(), - ); - } - return task; - }).toList(); + state = state.map((task) { + if (task.taskId == taskId) { + return task.copyWith( + status: DriveTaskStatus.failed, + errorMessage: errorMessage, + updatedAt: DateTime.now(), + ); + } + return task; + }).toList(); } void addUploadTask(DriveTask task) { @@ -259,45 +257,46 @@ class UploadTasksNotifier extends Notifier> { DriveTaskStatus status, { String? errorMessage, }) { - state = - state.map((task) { - if (task.taskId == taskId) { - return task.copyWith( - status: status, - errorMessage: errorMessage, - updatedAt: DateTime.now(), - ); - } - return task; - }).toList(); + state = state.map((task) { + if (task.taskId == taskId) { + return task.copyWith( + status: status, + errorMessage: errorMessage, + updatedAt: DateTime.now(), + ); + } + return task; + }).toList(); } void updateTransmissionProgress(String taskId, double progress) { - state = - state.map((task) { - if (task.taskId == taskId) { - return task.copyWith( - transmissionProgress: progress, - updatedAt: DateTime.now(), - ); - } - return task; - }).toList(); + state = state.map((task) { + if (task.taskId == taskId) { + return task.copyWith( + transmissionProgress: progress, + updatedAt: DateTime.now(), + ); + } + return task; + }).toList(); } - void updateUploadProgress(String taskId, int uploadedBytes, int uploadedChunks) { - state = - state.map((task) { - if (task.taskId == taskId) { - return task.copyWith( - uploadedBytes: uploadedBytes, - uploadedChunks: uploadedChunks, - status: DriveTaskStatus.inProgress, - updatedAt: DateTime.now(), - ); - } - return task; - }).toList(); + void updateUploadProgress( + String taskId, + int uploadedBytes, + int uploadedChunks, + ) { + state = state.map((task) { + if (task.taskId == taskId) { + return task.copyWith( + uploadedBytes: uploadedBytes, + uploadedChunks: uploadedChunks, + status: DriveTaskStatus.inProgress, + updatedAt: DateTime.now(), + ); + } + return task; + }).toList(); } void updateDownloadProgress( @@ -305,17 +304,16 @@ class UploadTasksNotifier extends Notifier> { int downloadedBytes, int totalBytes, ) { - state = - state.map((task) { - if (task.taskId == taskId) { - return task.copyWith( - fileSize: totalBytes, - uploadedBytes: downloadedBytes, - updatedAt: DateTime.now(), - ); - } - return task; - }).toList(); + state = state.map((task) { + if (task.taskId == taskId) { + return task.copyWith( + fileSize: totalBytes, + uploadedBytes: downloadedBytes, + updatedAt: DateTime.now(), + ); + } + return task; + }).toList(); } void removeTask(String taskId) { @@ -323,16 +321,15 @@ class UploadTasksNotifier extends Notifier> { } void clearCompletedTasks() { - state = - state - .where( - (task) => - task.status != DriveTaskStatus.completed && - task.status != DriveTaskStatus.failed && - task.status != DriveTaskStatus.cancelled && - task.status != DriveTaskStatus.expired, - ) - .toList(); + state = state + .where( + (task) => + task.status != DriveTaskStatus.completed && + task.status != DriveTaskStatus.failed && + task.status != DriveTaskStatus.cancelled && + task.status != DriveTaskStatus.expired, + ) + .toList(); } void clearAllTasks() { @@ -550,8 +547,9 @@ class EnhancedFileUploader extends FileUploader { // Use old way for Uint8List final chunks = []; for (int i = 0; i < fileData.length; i += chunkSize) { - final end = - i + chunkSize > fileData.length ? fileData.length : i + chunkSize; + final end = i + chunkSize > fileData.length + ? fileData.length + : i + chunkSize; chunks.add(Uint8List.fromList(fileData.sublist(i, end))); } @@ -585,4 +583,4 @@ class EnhancedFileUploader extends FileUploader { onProgress?.call(null, Duration.zero); return await completeUpload(taskId); } -} \ No newline at end of file +} diff --git a/lib/models/drive_task.dart b/lib/drive/drive_models/drive_task.dart similarity index 96% rename from lib/models/drive_task.dart rename to lib/drive/drive_models/drive_task.dart index 8e963610..e9ba4518 100644 --- a/lib/models/drive_task.dart +++ b/lib/drive/drive_models/drive_task.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/file.dart'; +import 'package:island/drive/drive_models/file.dart'; part 'drive_task.freezed.dart'; part 'drive_task.g.dart'; diff --git a/lib/models/drive_task.freezed.dart b/lib/drive/drive_models/drive_task.freezed.dart similarity index 100% rename from lib/models/drive_task.freezed.dart rename to lib/drive/drive_models/drive_task.freezed.dart diff --git a/lib/models/drive_task.g.dart b/lib/drive/drive_models/drive_task.g.dart similarity index 100% rename from lib/models/drive_task.g.dart rename to lib/drive/drive_models/drive_task.g.dart diff --git a/lib/models/file.dart b/lib/drive/drive_models/file.dart similarity index 98% rename from lib/models/file.dart rename to lib/drive/drive_models/file.dart index 71c26659..c289f454 100644 --- a/lib/models/file.dart +++ b/lib/drive/drive_models/file.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/file_pool.dart'; +import 'package:island/drive/drive_models/file_pool.dart'; part 'file.freezed.dart'; part 'file.g.dart'; diff --git a/lib/models/file.freezed.dart b/lib/drive/drive_models/file.freezed.dart similarity index 100% rename from lib/models/file.freezed.dart rename to lib/drive/drive_models/file.freezed.dart diff --git a/lib/models/file.g.dart b/lib/drive/drive_models/file.g.dart similarity index 100% rename from lib/models/file.g.dart rename to lib/drive/drive_models/file.g.dart diff --git a/lib/models/file_list_item.dart b/lib/drive/drive_models/file_list_item.dart similarity index 87% rename from lib/models/file_list_item.dart rename to lib/drive/drive_models/file_list_item.dart index 07025d04..b6977ed4 100644 --- a/lib/models/file_list_item.dart +++ b/lib/drive/drive_models/file_list_item.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/file.dart'; +import 'package:island/drive/drive_models/file.dart'; part 'file_list_item.freezed.dart'; diff --git a/lib/models/file_list_item.freezed.dart b/lib/drive/drive_models/file_list_item.freezed.dart similarity index 100% rename from lib/models/file_list_item.freezed.dart rename to lib/drive/drive_models/file_list_item.freezed.dart diff --git a/lib/models/file_pool.dart b/lib/drive/drive_models/file_pool.dart similarity index 100% rename from lib/models/file_pool.dart rename to lib/drive/drive_models/file_pool.dart diff --git a/lib/models/file_pool.freezed.dart b/lib/drive/drive_models/file_pool.freezed.dart similarity index 100% rename from lib/models/file_pool.freezed.dart rename to lib/drive/drive_models/file_pool.freezed.dart diff --git a/lib/models/file_pool.g.dart b/lib/drive/drive_models/file_pool.g.dart similarity index 100% rename from lib/models/file_pool.g.dart rename to lib/drive/drive_models/file_pool.g.dart diff --git a/lib/models/folder.dart b/lib/drive/drive_models/folder.dart similarity index 100% rename from lib/models/folder.dart rename to lib/drive/drive_models/folder.dart diff --git a/lib/models/folder.freezed.dart b/lib/drive/drive_models/folder.freezed.dart similarity index 100% rename from lib/models/folder.freezed.dart rename to lib/drive/drive_models/folder.freezed.dart diff --git a/lib/models/folder.g.dart b/lib/drive/drive_models/folder.g.dart similarity index 100% rename from lib/models/folder.g.dart rename to lib/drive/drive_models/folder.g.dart diff --git a/lib/services/file_uploader.dart b/lib/drive/drive_service.dart similarity index 78% rename from lib/services/file_uploader.dart rename to lib/drive/drive_service.dart index c3a60014..d29f2b7a 100644 --- a/lib/services/file_uploader.dart +++ b/lib/drive/drive_service.dart @@ -1,16 +1,22 @@ import 'dart:async'; +import 'dart:io'; import 'package:convert/convert.dart'; import 'package:cross_file/cross_file.dart'; import 'package:crypto/crypto.dart'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/drive/upload_tasks.dart'; +import 'package:island/drive/drive_models/drive_task.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/core/network.dart'; +import 'package:island/drive/drive/upload_tasks.dart'; +import 'package:island/shared/widgets/alert.dart'; import 'package:mime/mime.dart'; import 'package:native_exif/native_exif.dart'; import 'package:path/path.dart' show extension; +import 'package:file_saver/file_saver.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:gal/gal.dart'; class FileUploader { final Dio _client; @@ -445,3 +451,118 @@ final fileUploaderProvider = Provider((ref) { final dio = ref.watch(apiClientProvider); return FileUploader(dio); }); + +class FileDownloadService { + final WidgetRef ref; + + FileDownloadService(this.ref); + + String _getFileExtension(SnCloudFile item) { + var extName = extension(item.name).trim(); + if (extName.isEmpty) { + extName = item.mimeType?.split('/').lastOrNull ?? 'jpeg'; + } + return extName.replaceFirst('.', ''); + } + + String _getFileName(SnCloudFile item, String extName) { + return item.name.isEmpty ? '${item.id}.$extName' : item.name; + } + + Future _downloadToTemp(SnCloudFile item, String extName) async { + final client = ref.read(apiClientProvider); + final tempDir = await getTemporaryDirectory(); + final filePath = '${tempDir.path}/${item.id}.$extName'; + + await client.download( + '/drive/files/${item.id}', + filePath, + queryParameters: {'original': true}, + ); + + return filePath; + } + + Future saveToGallery(SnCloudFile item) async { + try { + showSnackBar('Saving image...'); + + final extName = _getFileExtension(item); + final filePath = await _downloadToTemp(item, extName); + + if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { + await Gal.putImage(filePath, album: 'Solar Network'); + showSnackBar('Image saved to gallery'); + } else { + await FileSaver.instance.saveFile( + name: _getFileName(item, extName), + file: File(filePath), + ); + showSnackBar('Image saved to downloads'); + } + } catch (e) { + showErrorAlert(e); + } + } + + Future downloadFile(SnCloudFile item) async { + try { + showSnackBar('Downloading file...'); + + final extName = _getFileExtension(item); + final filePath = await _downloadToTemp(item, extName); + + await FileSaver.instance.saveFile( + name: _getFileName(item, extName), + file: File(filePath), + ); + showSnackBar('File saved to downloads'); + } catch (e) { + showErrorAlert(e); + } + } + + Future downloadWithProgress( + SnCloudFile item, { + void Function(int received, int total)? onProgress, + }) async { + final taskNotifier = ref.read(uploadTasksProvider.notifier); + final taskId = taskNotifier.addLocalDownloadTask(item); + + try { + showSnackBar('Downloading file...'); + + final client = ref.read(apiClientProvider); + final extName = _getFileExtension(item); + final tempDir = await getTemporaryDirectory(); + final filePath = '${tempDir.path}/${item.id}.$extName'; + + await client.download( + '/drive/files/${item.id}', + filePath, + queryParameters: {'original': true}, + onReceiveProgress: (count, total) { + onProgress?.call(count, total); + if (total > 0) { + taskNotifier.updateDownloadProgress(taskId, count, total); + taskNotifier.updateTransmissionProgress(taskId, count / total); + } + }, + ); + + await FileSaver.instance.saveFile( + name: _getFileName(item, extName), + file: File(filePath), + ); + taskNotifier.updateTaskStatus(taskId, DriveTaskStatus.completed); + showSnackBar('File saved to downloads'); + } catch (e) { + taskNotifier.updateTaskStatus( + taskId, + DriveTaskStatus.failed, + errorMessage: e.toString(), + ); + showErrorAlert(e); + } + } +} diff --git a/lib/widgets/content/cloud_files.dart b/lib/drive/drive_widgets/cloud_files.dart similarity index 98% rename from lib/widgets/content/cloud_files.dart rename to lib/drive/drive_widgets/cloud_files.dart index 2cbfcbd4..1afcd7e5 100644 --- a/lib/widgets/content/cloud_files.dart +++ b/lib/drive/drive_widgets/cloud_files.dart @@ -5,20 +5,19 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/services/time.dart'; -import 'package:island/utils/format.dart'; -import 'package:island/widgets/content/profile_decoration.dart'; +import 'package:island/core/widgets/content/file_viewer_contents.dart'; +import 'package:island/core/widgets/content/image.dart'; +import 'package:island/core/widgets/content/video.native.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/core/utils/format.dart'; +import 'package:island/core/widgets/content/profile_decoration.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'dart:math' as math; import 'dart:ui' as ui; -import 'package:island/widgets/data_saving_gate.dart'; - -import 'file_viewer_contents.dart'; -import 'image.dart'; -import 'video.dart'; +import 'package:island/core/data_saving_gate.dart'; class CloudFileWidget extends HookConsumerWidget { final SnCloudFile item; diff --git a/lib/widgets/file_list_view.dart b/lib/drive/drive_widgets/file_list_view.dart similarity index 98% rename from lib/widgets/file_list_view.dart rename to lib/drive/drive_widgets/file_list_view.dart index 278e7cd9..1ce366dd 100644 --- a/lib/widgets/file_list_view.dart +++ b/lib/drive/drive_widgets/file_list_view.dart @@ -8,20 +8,20 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file_list_item.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/file_pool.dart'; -import 'package:island/pods/drive/file_list.dart'; -import 'package:island/pods/drive/file_pool.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/services/file_uploader.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/utils/file_icon_utils.dart'; -import 'package:island/utils/format.dart'; +import 'package:island/drive/drive_models/file_list_item.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/drive/drive_models/file_pool.dart'; +import 'package:island/drive/drive/file_list.dart'; +import 'package:island/drive/drive/file_pool.dart'; +import 'package:island/core/network.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/core/utils/file_icon_utils.dart'; +import 'package:island/core/utils/format.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/files/file_detail.dart b/lib/drive/files/file_detail.dart similarity index 93% rename from lib/screens/files/file_detail.dart rename to lib/drive/files/file_detail.dart index 83220ab3..4024fdb9 100644 --- a/lib/screens/files/file_detail.dart +++ b/lib/drive/files/file_detail.dart @@ -3,13 +3,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/services/file_download.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/file_info_sheet.dart'; -import 'package:island/widgets/content/file_viewer_contents.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/core/widgets/content/file_info_sheet.dart'; +import 'package:island/core/widgets/content/file_viewer_contents.dart'; class FileDetailScreen extends HookConsumerWidget { final SnCloudFile item; diff --git a/lib/screens/files/file_list.dart b/lib/drive/files/file_list.dart similarity index 95% rename from lib/screens/files/file_list.dart rename to lib/drive/files/file_list.dart index 0399d2f9..d28a0303 100644 --- a/lib/screens/files/file_list.dart +++ b/lib/drive/files/file_list.dart @@ -5,15 +5,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/file_pool.dart'; -import 'package:island/pods/drive/file_list.dart'; -import 'package:island/services/file_uploader.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/file_list_view.dart'; -import 'package:island/widgets/usage_overview.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/drive/drive_models/file_pool.dart'; +import 'package:island/drive/drive/file_list.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/drive/drive_widgets/file_list_view.dart'; +import 'package:island/accounts/usage_overview.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/services/fitness_data.dart b/lib/fitness/fitness_data.dart similarity index 100% rename from lib/services/fitness_data.dart rename to lib/fitness/fitness_data.dart diff --git a/lib/screens/fitness_activity.dart b/lib/fitness/fitness_screen.dart similarity index 98% rename from lib/screens/fitness_activity.dart rename to lib/fitness/fitness_screen.dart index 22acbe1a..48fbfe5a 100644 --- a/lib/screens/fitness_activity.dart +++ b/lib/fitness/fitness_screen.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:health/health.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/services/fitness_service.dart'; -import 'package:island/services/fitness_data.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/fitness/fitness_service.dart'; +import 'package:island/fitness/fitness_data.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:table_calendar/table_calendar.dart'; import 'dart:math'; @@ -537,11 +537,7 @@ class FitnessActivityScreen extends HookConsumerWidget { color: Colors.blue.withOpacity(0.8), shape: BoxShape.circle, boxShadow: [ - BoxShadow( - color: Colors.black26, - blurRadius: 2, - offset: Offset(0, 1), - ), + BoxShadow(color: Colors.black26, blurRadius: 2, offset: Offset(0, 1)), ], ), child: Center( diff --git a/lib/services/fitness_service.dart b/lib/fitness/fitness_service.dart similarity index 99% rename from lib/services/fitness_service.dart rename to lib/fitness/fitness_service.dart index 0563f334..5e483bd0 100644 --- a/lib/services/fitness_service.dart +++ b/lib/fitness/fitness_service.dart @@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:health/health.dart'; import 'package:island/talker.dart'; -import 'package:island/services/fitness_data.dart'; +import 'package:island/fitness/fitness_data.dart'; final fitnessServiceProvider = Provider((ref) { return FitnessService(); diff --git a/lib/screens/lottery.dart b/lib/lotteries/lottery.dart similarity index 98% rename from lib/screens/lottery.dart rename to lib/lotteries/lottery.dart index 683856e9..9b1c99f4 100644 --- a/lib/screens/lottery.dart +++ b/lib/lotteries/lottery.dart @@ -2,13 +2,13 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/wallet.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/wallet.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/payment/payment_overlay.dart'; +import 'package:island/wallet/wallet.dart'; +import 'package:island/wallet/wallet_models/wallet.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/core/widgets/payment/payment_overlay.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/lottery.g.dart b/lib/lotteries/lottery.g.dart similarity index 96% rename from lib/screens/lottery.g.dart rename to lib/lotteries/lottery.g.dart index 6c77b4d9..1be7be04 100644 --- a/lib/screens/lottery.g.dart +++ b/lib/lotteries/lottery.g.dart @@ -66,7 +66,7 @@ final class LotteryTicketsProvider } } -String _$lotteryTicketsHash() => r'dd17cd721fc3b176ffa0ee0a85d0d850740e5e80'; +String _$lotteryTicketsHash() => r'a6cfdc85dcc05a7d6beec1a31cc9e8838fc4878c'; final class LotteryTicketsFamily extends $Family with @@ -150,7 +150,7 @@ final class LotteryRecordsProvider } } -String _$lotteryRecordsHash() => r'55c657460f18d9777741d09013b445ca036863f3'; +String _$lotteryRecordsHash() => r'127d680d65ec9b63eec1fe60d6a8f11bb7bfe782'; final class LotteryRecordsFamily extends $Family with diff --git a/lib/main.dart b/lib/main.dart index 3054683b..9dfd7c22 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,22 +12,22 @@ import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hotkey_manager/hotkey_manager.dart'; import 'package:image_picker_android/image_picker_android.dart'; -import 'package:island/services/analytics_service.dart'; +import 'package:island/core/services/analytics_service.dart'; import 'package:island/talker.dart'; import 'package:island/firebase_options.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/audio.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/theme.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/pods/websocket.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/audio.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/theme.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/core/websocket.dart'; import 'package:island/route.dart'; -import 'package:island/services/notify.dart'; -import 'package:island/services/widget_sync_service.dart'; -import 'package:island/services/timezone.dart'; -import 'package:island/services/quick_actions.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/core/services/notify.dart'; +import 'package:island/core/services/widget_sync_service.dart'; +import 'package:island/core/services/timezone.dart'; +import 'package:island/core/services/quick_actions.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; import 'package:relative_time/relative_time.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; diff --git a/lib/screens/notification.dart b/lib/notifications/notification.dart similarity index 93% rename from lib/screens/notification.dart rename to lib/notifications/notification.dart index 485bc78d..48b94b76 100644 --- a/lib/screens/notification.dart +++ b/lib/notifications/notification.dart @@ -5,15 +5,15 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/pods/websocket.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/markdown.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/notification_tile.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/network.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/core/websocket.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/markdown.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/notifications/notification_tile.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:skeletonizer/skeletonizer.dart'; diff --git a/lib/screens/notification.g.dart b/lib/notifications/notification.g.dart similarity index 100% rename from lib/screens/notification.g.dart rename to lib/notifications/notification.g.dart diff --git a/lib/widgets/notification_item.dart b/lib/notifications/notification_item.dart similarity index 98% rename from lib/widgets/notification_item.dart rename to lib/notifications/notification_item.dart index 3ccaea09..2342576d 100644 --- a/lib/widgets/notification_item.dart +++ b/lib/notifications/notification_item.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/notification.dart'; +import 'package:island/core/notification.dart'; import 'package:island/route.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher_string.dart'; diff --git a/lib/widgets/notification_overlay.dart b/lib/notifications/notification_overlay.dart similarity index 96% rename from lib/widgets/notification_overlay.dart rename to lib/notifications/notification_overlay.dart index 91367580..acf10186 100644 --- a/lib/widgets/notification_overlay.dart +++ b/lib/notifications/notification_overlay.dart @@ -4,9 +4,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/notification.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/notification_item.dart'; +import 'package:island/core/notification.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/notifications/notification_item.dart'; import 'package:styled_widget/styled_widget.dart'; class NotificationOverlay extends HookConsumerWidget { diff --git a/lib/widgets/notification_tile.dart b/lib/notifications/notification_tile.dart similarity index 97% rename from lib/widgets/notification_tile.dart rename to lib/notifications/notification_tile.dart index d6e4b390..e539adff 100644 --- a/lib/widgets/notification_tile.dart +++ b/lib/notifications/notification_tile.dart @@ -1,10 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:island/models/account.dart'; +import 'package:island/accounts/accounts_models/account.dart'; import 'package:island/route.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/markdown.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/markdown.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:relative_time/relative_time.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/pods/paging.dart b/lib/pagination/pagination.dart similarity index 100% rename from lib/pods/paging.dart rename to lib/pagination/pagination.dart diff --git a/lib/pods/database.dart b/lib/pods/database.dart deleted file mode 100644 index 5a4dd2ae..00000000 --- a/lib/pods/database.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/database/drift_db.dart'; - -import 'package:island/database/database.native.dart' - if (dart.library.html) 'package:island/database/database.web.dart'; - -final databaseProvider = Provider((ref) { - final db = constructDb(); - ref.onDispose(() => db.close()); - return db; -}); diff --git a/lib/screens/poll/poll_editor.dart b/lib/polls/poll/poll_editor.dart similarity index 70% rename from lib/screens/poll/poll_editor.dart rename to lib/polls/poll/poll_editor.dart index b16233d7..80d120b0 100644 --- a/lib/screens/poll/poll_editor.dart +++ b/lib/polls/poll/poll_editor.dart @@ -4,11 +4,11 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:dio/dio.dart'; import 'package:gap/gap.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/core/network.dart'; import 'package:island/talker.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/models/poll.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/posts/posts_models/poll.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:uuid/uuid.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -78,8 +78,8 @@ class PollEditor extends Notifier { final dynamic payload = res.data; final Map json = payload is Map && payload['data'] is Map - ? Map.from(payload['data'] as Map) - : Map.from(payload as Map); + ? Map.from(payload['data'] as Map) + : Map.from(payload as Map); final poll = SnPoll.fromJson(json); @@ -106,16 +106,15 @@ class PollEditor extends Notifier { final q = SnPollQuestion( id: const Uuid().v4(), type: type, - options: - isOptionsType - ? [ - SnPollOption( - id: const Uuid().v4(), - label: 'pollOptionDefaultLabel'.tr(), - order: 0, - ), - ] - : null, + options: isOptionsType + ? [ + SnPollOption( + id: const Uuid().v4(), + label: 'pollOptionDefaultLabel'.tr(), + order: 0, + ), + ] + : null, title: '', description: null, order: nextOrder, @@ -185,18 +184,17 @@ class PollEditor extends Notifier { if (index < 0 || index >= state.questions.length) return; final q = state.questions[index]; final isOptionsType = _isOptionsType(type); - final newOptions = - isOptionsType - ? (q.options?.isNotEmpty == true - ? q.options - : [ + final newOptions = isOptionsType + ? (q.options?.isNotEmpty == true + ? q.options + : [ SnPollOption( id: const Uuid().v4(), label: 'pollOptionDefaultLabel'.tr(), order: 0, ), ]) - : null; + : null; _updateQuestion(index, q.copyWith(type: type, options: newOptions)); } @@ -349,46 +347,44 @@ class PollEditorScreen extends ConsumerWidget { 'title': model.title, 'description': model.description, 'endedAt': model.endedAt?.toUtc().toIso8601String(), - 'questions': - model.questions - .map( - (q) => { - 'type': q.type.index, - 'options': - q.options - ?.map( - (o) => { - 'label': o.label, - 'description': o.description, - 'order': o.order, - }, - ) - .toList(), - 'title': q.title, - 'description': q.description, - 'order': q.order, - 'isRequired': q.isRequired, - }, - ) - .toList(), + 'questions': model.questions + .map( + (q) => { + 'type': q.type.index, + 'options': q.options + ?.map( + (o) => { + 'label': o.label, + 'description': o.description, + 'order': o.order, + }, + ) + .toList(), + 'title': q.title, + 'description': q.description, + 'order': q.order, + 'isRequired': q.isRequired, + }, + ) + .toList(), }; try { final isUpdate = model.id != null && model.id!.isNotEmpty; - final String path = - isUpdate ? '/sphere/polls/${model.id}' : '/sphere/polls'; - final Response res = - await (isUpdate - ? dio.patch( - path, - queryParameters: {'pub': initialPublisher}, - data: body, - ) - : dio.post( - path, - queryParameters: {'pub': initialPublisher}, - data: body, - )); + final String path = isUpdate + ? '/sphere/polls/${model.id}' + : '/sphere/polls'; + final Response res = await (isUpdate + ? dio.patch( + path, + queryParameters: {'pub': initialPublisher}, + data: body, + ) + : dio.post( + path, + queryParameters: {'pub': initialPublisher}, + data: body, + )); showSnackBar(isUpdate ? 'pollUpdated'.tr() : 'pollCreated'.tr()); @@ -431,24 +427,23 @@ class PollEditorScreen extends ConsumerWidget { onClose: () async { final confirmed = await showDialog( context: context, - builder: - (ctx) => AlertDialog( - title: Text('confirm'.tr()), - content: Text('pollConfirmDiscard'.tr()), - actions: [ - TextButton( - onPressed: () => Navigator.of(ctx).pop(false), - child: Text('cancel'.tr()), - ), - TextButton( - onPressed: () => Navigator.of(ctx).pop(true), - style: TextButton.styleFrom( - foregroundColor: Theme.of(ctx).colorScheme.error, - ), - child: Text('discard'.tr()), - ), - ], + builder: (ctx) => AlertDialog( + title: Text('confirm'.tr()), + content: Text('pollConfirmDiscard'.tr()), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(false), + child: Text('cancel'.tr()), ), + TextButton( + onPressed: () => Navigator.of(ctx).pop(true), + style: TextButton.styleFrom( + foregroundColor: Theme.of(ctx).colorScheme.error, + ), + child: Text('discard'.tr()), + ), + ], + ), ); if (confirmed == true) { if (context.mounted) Navigator.of(context).pop(); @@ -459,171 +454,157 @@ class PollEditorScreen extends ConsumerWidget { Expanded( child: ConstrainedBox( constraints: BoxConstraints(maxWidth: 640), - child: - Form( - key: ValueKey(model.id), - child: ListView( - padding: const EdgeInsets.all(16), + child: Form( + key: ValueKey(model.id), + child: ListView( + padding: const EdgeInsets.all(16), + children: [ + TextFormField( + initialValue: model.title ?? '', + decoration: InputDecoration( + labelText: 'postTitle'.tr(), + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + ), + textInputAction: TextInputAction.next, + maxLength: 256, + onChanged: notifier.setTitle, + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + validator: (v) { + if (v == null || v.trim().isEmpty) { + return 'pollTitleRequired'.tr(); + } + return null; + }, + ), + const Gap(12), + TextFormField( + initialValue: model.description ?? '', + decoration: InputDecoration( + labelText: 'description'.tr(), + alignLabelWithHint: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + ), + maxLines: 3, + maxLength: 4096, + onChanged: notifier.setDescription, + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), + ), + const Gap(12), + _EndDatePicker( + value: model.endedAt, + onChanged: notifier.setEndedAt, + ), + const Gap(24), + Row( children: [ - TextFormField( - initialValue: model.title ?? '', - decoration: InputDecoration( - labelText: 'postTitle'.tr(), - border: OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(16), - ), - ), - ), - textInputAction: TextInputAction.next, - maxLength: 256, - onChanged: notifier.setTitle, - onTapOutside: - (_) => - FocusManager.instance.primaryFocus?.unfocus(), - validator: (v) { - if (v == null || v.trim().isEmpty) { - return 'pollTitleRequired'.tr(); - } - return null; - }, + Text( + 'questions'.tr(), + style: Theme.of(context).textTheme.titleLarge, ), - const Gap(12), - TextFormField( - initialValue: model.description ?? '', - decoration: InputDecoration( - labelText: 'description'.tr(), - alignLabelWithHint: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(16), - ), - ), - ), - maxLines: 3, - maxLength: 4096, - onChanged: notifier.setDescription, - onTapOutside: - (_) => - FocusManager.instance.primaryFocus?.unfocus(), - ), - const Gap(12), - _EndDatePicker( - value: model.endedAt, - onChanged: notifier.setEndedAt, - ), - const Gap(24), - Row( - children: [ - Text( - 'questions'.tr(), - style: Theme.of(context).textTheme.titleLarge, - ), - const Spacer(), - MenuAnchor( - builder: (context, controller, child) { - return FilledButton.icon( - onPressed: () { - controller.isOpen - ? controller.close() - : controller.open(); - }, - icon: const Icon(Icons.add), - label: Text('pollAddQuestion'.tr()), - ); + const Spacer(), + MenuAnchor( + builder: (context, controller, child) { + return FilledButton.icon( + onPressed: () { + controller.isOpen + ? controller.close() + : controller.open(); }, - menuChildren: - SnPollQuestionType.values - .map( - (t) => MenuItemButton( - leadingIcon: Icon(_iconForType(t)), - onPressed: - () => notifier.addQuestion(t), - child: Text(_labelForType(t)), - ), - ) - .toList(), - ), - ], - ), - const Gap(8), - if (model.questions.isEmpty) - _EmptyState( - title: 'pollNoQuestionsYet'.tr(), - subtitle: 'pollNoQuestionsHint'.tr(), - ) - else - ReorderableListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: model.questions.length, - onReorder: (oldIndex, newIndex) { - // Convert to stepwise moves using provided functions - if (newIndex > oldIndex) newIndex -= 1; - final steps = newIndex - oldIndex; - if (steps == 0) return; - if (steps > 0) { - for (int i = 0; i < steps; i++) { - notifier.moveQuestionDown(oldIndex + i); - } - } else { - for (int i = 0; i > steps; i--) { - notifier.moveQuestionUp(oldIndex + i); - } - } - }, - buildDefaultDragHandles: false, - itemBuilder: (context, index) { - final q = model.questions[index]; - return Card( - key: ValueKey('q_$index'), - margin: const EdgeInsets.symmetric(vertical: 8), - clipBehavior: Clip.antiAlias, - child: Column( - children: [ - _QuestionHeader( - index: index, - question: q, - onMoveUp: - index > 0 - ? () => - notifier.moveQuestionUp(index) - : null, - onMoveDown: - index < model.questions.length - 1 - ? () => notifier.moveQuestionDown( - index, - ) - : null, - onDelete: - () => notifier.removeQuestion(index), - ), - const Divider(height: 1), - Padding( - padding: const EdgeInsets.all(16), - child: _QuestionEditor( - index: index, - question: q, - ), - ), - ], + icon: const Icon(Icons.add), + label: Text('pollAddQuestion'.tr()), + ); + }, + menuChildren: SnPollQuestionType.values + .map( + (t) => MenuItemButton( + leadingIcon: Icon(_iconForType(t)), + onPressed: () => notifier.addQuestion(t), + child: Text(_labelForType(t)), ), - ); - }, - ), - const Gap(96), + ) + .toList(), + ), ], ), - ).center(), + const Gap(8), + if (model.questions.isEmpty) + _EmptyState( + title: 'pollNoQuestionsYet'.tr(), + subtitle: 'pollNoQuestionsHint'.tr(), + ) + else + ReorderableListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: model.questions.length, + onReorder: (oldIndex, newIndex) { + // Convert to stepwise moves using provided functions + if (newIndex > oldIndex) newIndex -= 1; + final steps = newIndex - oldIndex; + if (steps == 0) return; + if (steps > 0) { + for (int i = 0; i < steps; i++) { + notifier.moveQuestionDown(oldIndex + i); + } + } else { + for (int i = 0; i > steps; i--) { + notifier.moveQuestionUp(oldIndex + i); + } + } + }, + buildDefaultDragHandles: false, + itemBuilder: (context, index) { + final q = model.questions[index]; + return Card( + key: ValueKey('q_$index'), + margin: const EdgeInsets.symmetric(vertical: 8), + clipBehavior: Clip.antiAlias, + child: Column( + children: [ + _QuestionHeader( + index: index, + question: q, + onMoveUp: index > 0 + ? () => notifier.moveQuestionUp(index) + : null, + onMoveDown: index < model.questions.length - 1 + ? () => notifier.moveQuestionDown(index) + : null, + onDelete: () => + notifier.removeQuestion(index), + ), + const Divider(height: 1), + Padding( + padding: const EdgeInsets.all(16), + child: _QuestionEditor( + index: index, + question: q, + ), + ), + ], + ), + ); + }, + ), + const Gap(96), + ], + ), + ).center(), ), ), Material( elevation: 2, color: Theme.of(context).colorScheme.surfaceContainer, - child: - ConstrainedBox( - constraints: BoxConstraints(maxWidth: 640), - child: Row( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 640), + child: + Row( children: [ OutlinedButton.icon( onPressed: () { @@ -648,7 +629,7 @@ class PollEditorScreen extends ConsumerWidget { top: 16, bottom: MediaQuery.of(context).padding.bottom + 16, ), - ).center(), + ).center(), ), ], ), @@ -688,19 +669,16 @@ class PollEditorScreen extends ConsumerWidget { buf.writeln('}'); showDialog( context: context, - builder: - (_) => AlertDialog( - title: Text('pollDebugPreview'.tr()), - content: SingleChildScrollView( - child: SelectableText(buf.toString()), - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text('close'.tr()), - ), - ], + builder: (_) => AlertDialog( + title: Text('pollDebugPreview'.tr()), + content: SingleChildScrollView(child: SelectableText(buf.toString())), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('close'.tr()), ), + ], + ), ); } } @@ -766,8 +744,8 @@ class _EndDatePicker extends StatelessWidget { value == null ? 'notSet'.tr() : MaterialLocalizations.of( - context, - ).formatFullDate(value!), + context, + ).formatFullDate(value!), ), if (value != null) ...[ const Text('—'), @@ -948,9 +926,8 @@ class _QuestionEditor extends ConsumerWidget { ), maxLines: 2, maxLength: 4096, - onChanged: - (v) => - notifier.setQuestionDescription(index, v.isEmpty ? null : v), + onChanged: (v) => + notifier.setQuestionDescription(index, v.isEmpty ? null : v), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), ), if (question.options != null) ...[ @@ -996,21 +973,20 @@ class _QuestionTypePicker extends StatelessWidget { borderRadius: BorderRadius.all(Radius.circular(16)), ), ), - items: - SnPollQuestionType.values - .map( - (t) => DropdownMenuItem( - value: t, - child: Row( - children: [ - Icon(_iconForType(t)), - const Gap(8), - Text(_labelForType(t)), - ], - ), - ), - ) - .toList(), + items: SnPollQuestionType.values + .map( + (t) => DropdownMenuItem( + value: t, + child: Row( + children: [ + Icon(_iconForType(t)), + const Gap(8), + Text(_labelForType(t)), + ], + ), + ), + ) + .toList(), onChanged: (t) { if (t != null) onChanged(t); }, @@ -1047,8 +1023,8 @@ class _OptionsEditor extends ConsumerWidget { ), ), onChanged: (v) => notifier.setOptionLabel(index, i, v), - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), inputFormatters: [LengthLimitingTextInputFormatter(1024)], ), ), @@ -1057,8 +1033,9 @@ class _OptionsEditor extends ConsumerWidget { width: 40, child: IconButton( tooltip: 'moveUp'.tr(), - onPressed: - i > 0 ? () => notifier.moveOptionUp(index, i) : null, + onPressed: i > 0 + ? () => notifier.moveOptionUp(index, i) + : null, icon: const Icon(Icons.arrow_upward), ), ), @@ -1066,10 +1043,9 @@ class _OptionsEditor extends ConsumerWidget { width: 40, child: IconButton( tooltip: 'moveDown'.tr(), - onPressed: - i < options.length - 1 - ? () => notifier.moveOptionDown(index, i) - : null, + onPressed: i < options.length - 1 + ? () => notifier.moveOptionDown(index, i) + : null, icon: const Icon(Icons.arrow_downward), ), ), @@ -1100,10 +1076,9 @@ class _TextAnswerPreview extends StatelessWidget { enabled: false, maxLines: long ? 4 : 1, decoration: InputDecoration( - labelText: - long - ? 'pollLongTextAnswerPreview'.tr() - : 'pollShortTextAnswerPreview'.tr(), + labelText: long + ? 'pollLongTextAnswerPreview'.tr() + : 'pollShortTextAnswerPreview'.tr(), border: const OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(16)), ), diff --git a/lib/widgets/poll/poll_feedback.dart b/lib/polls/polls_widgets/poll/poll_feedback.dart similarity index 92% rename from lib/widgets/poll/poll_feedback.dart rename to lib/polls/polls_widgets/poll/poll_feedback.dart index 156d97af..28f95c89 100644 --- a/lib/widgets/poll/poll_feedback.dart +++ b/lib/polls/polls_widgets/poll/poll_feedback.dart @@ -2,17 +2,17 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/poll.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/screens/creators/poll/poll_list.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/account/account_pfc.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; -import 'package:island/widgets/poll/poll_stats_widget.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/accounts/accounts_widgets/account/account_pfc.dart'; +import 'package:island/creators/creators/poll/poll_list.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/polls/polls_widgets/poll/poll_stats_widget.dart'; +import 'package:island/posts/posts_models/poll.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:styled_widget/styled_widget.dart'; final pollFeedbackNotifierProvider = AsyncNotifierProvider.autoDispose.family( @@ -238,7 +238,7 @@ class _PollAnswerTile extends StatelessWidget { radius: 16, child: Icon(Icons.how_to_vote, size: 16), ) - : AccountPfcGestureDetector( + : AccountPfcRegion( uname: answer.account!.name, child: ProfilePictureWidget( file: answer.account!.profile.picture, diff --git a/lib/widgets/poll/poll_stats_widget.dart b/lib/polls/polls_widgets/poll/poll_stats_widget.dart similarity index 99% rename from lib/widgets/poll/poll_stats_widget.dart rename to lib/polls/polls_widgets/poll/poll_stats_widget.dart index eb5bc946..d61aa20e 100644 --- a/lib/widgets/poll/poll_stats_widget.dart +++ b/lib/polls/polls_widgets/poll/poll_stats_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:island/models/poll.dart'; +import 'package:island/posts/posts_models/poll.dart'; class PollStatsWidget extends StatelessWidget { const PollStatsWidget({ diff --git a/lib/widgets/poll/poll_submit.dart b/lib/polls/polls_widgets/poll/poll_submit.dart similarity index 91% rename from lib/widgets/poll/poll_submit.dart rename to lib/polls/polls_widgets/poll/poll_submit.dart index 555c5b8e..ddb41ac6 100644 --- a/lib/widgets/poll/poll_submit.dart +++ b/lib/polls/polls_widgets/poll/poll_submit.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:island/models/poll.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/creators/poll/poll_list.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/poll/poll_stats_widget.dart'; +import 'package:island/creators/creators/poll/poll_list.dart'; +import 'package:island/polls/polls_widgets/poll/poll_stats_widget.dart'; +import 'package:island/posts/posts_models/poll.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; class PollSubmit extends ConsumerStatefulWidget { const PollSubmit({ @@ -368,11 +368,10 @@ class _PollSubmitState extends ConsumerState { RadioListTile( value: opt.id, groupValue: _singleChoiceSelected, - onChanged: - (val) => setState(() { - _singleChoiceSelected = val; - _userHasEdited = true; - }), + onChanged: (val) => setState(() { + _singleChoiceSelected = val; + _userHasEdited = true; + }), title: Text(opt.label), subtitle: opt.description != null ? Text(opt.description!) : null, ), @@ -490,30 +489,28 @@ class _PollSubmitState extends ConsumerState { OutlinedButton.icon( icon: const Icon(Icons.arrow_back), label: Text(_index == 0 ? 'cancel'.tr() : 'back'.tr()), - onPressed: - _submitting - ? null - : () { - if (_index == 0 && _isModifying) { - // If at first question and in modification mode, go back to submitted view - setState(() { - _isModifying = false; - }); - } else { - _back(); - } - }, + onPressed: _submitting + ? null + : () { + if (_index == 0 && _isModifying) { + // If at first question and in modification mode, go back to submitted view + setState(() { + _isModifying = false; + }); + } else { + _back(); + } + }, ), const Spacer(), FilledButton.icon( - icon: - _submitting - ? const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : Icon(isLast ? Icons.check : Icons.arrow_forward), + icon: _submitting + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : Icon(isLast ? Icons.check : Icons.arrow_forward), label: Text(isLast ? 'submit'.tr() : 'next'.tr()), onPressed: canProceed ? () => _next(poll) : null, ), @@ -544,11 +541,10 @@ class _PollSubmitState extends ConsumerState { padding: const EdgeInsets.only(left: 8), child: Text( '*', - style: Theme.of( - context, - ).textTheme.titleMedium?.copyWith( - color: Theme.of(context).colorScheme.error, - ), + style: Theme.of(context).textTheme.titleMedium + ?.copyWith( + color: Theme.of(context).colorScheme.error, + ), ), ), ], @@ -622,11 +618,10 @@ class _PollSubmitState extends ConsumerState { padding: const EdgeInsets.only(left: 8), child: Text( '*', - style: Theme.of( - context, - ).textTheme.titleMedium?.copyWith( - color: Theme.of(context).colorScheme.error, - ), + style: Theme.of(context).textTheme.titleMedium + ?.copyWith( + color: Theme.of(context).colorScheme.error, + ), ), ), ], @@ -723,20 +718,18 @@ class _PollSubmitState extends ConsumerState { final pollAsync = ref.watch(pollWithStatsProvider(widget.pollId)); return pollAsync.when( - loading: - () => const Center( - child: Padding( - padding: EdgeInsets.all(16.0), - child: CircularProgressIndicator(), - ), - ), - error: - (error, stack) => Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Text('Failed to load poll: $error'), - ), - ), + loading: () => const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + ), + ), + error: (error, stack) => Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text('Failed to load poll: $error'), + ), + ), data: (poll) { // Initialize questions when data is available _questions = [...poll.questions] @@ -766,12 +759,13 @@ class _PollSubmitState extends ConsumerState { AnimatedSwitcher( duration: const Duration(milliseconds: 300), transitionBuilder: (child, anim) { - final offset = Tween( - begin: const Offset(0, -0.1), - end: Offset.zero, - ).animate( - CurvedAnimation(parent: anim, curve: Curves.easeOut), - ); + final offset = + Tween( + begin: const Offset(0, -0.1), + end: Offset.zero, + ).animate( + CurvedAnimation(parent: anim, curve: Curves.easeOut), + ); final fade = CurvedAnimation( parent: anim, curve: Curves.easeOut, @@ -804,12 +798,13 @@ class _PollSubmitState extends ConsumerState { AnimatedSwitcher( duration: const Duration(milliseconds: 300), transitionBuilder: (child, anim) { - final offset = Tween( - begin: const Offset(0, -0.1), - end: Offset.zero, - ).animate( - CurvedAnimation(parent: anim, curve: Curves.easeOut), - ); + final offset = + Tween( + begin: const Offset(0, -0.1), + end: Offset.zero, + ).animate( + CurvedAnimation(parent: anim, curve: Curves.easeOut), + ); final fade = CurvedAnimation( parent: anim, curve: Curves.easeOut, diff --git a/lib/widgets/activity_heatmap.dart b/lib/posts/activity_heatmap.dart similarity index 99% rename from lib/widgets/activity_heatmap.dart rename to lib/posts/activity_heatmap.dart index c40feb77..9e1a04c8 100644 --- a/lib/widgets/activity_heatmap.dart +++ b/lib/posts/activity_heatmap.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/heatmap.dart'; -import 'package:island/services/responsive.dart'; +import 'package:island/posts/posts_models/heatmap.dart'; +import 'package:island/core/services/responsive.dart'; /// Custom data class for selected heatmap item class SelectedHeatmapItem { diff --git a/lib/screens/posts/compose.dart b/lib/posts/compose.dart similarity index 84% rename from lib/screens/posts/compose.dart rename to lib/posts/compose.dart index aca519a6..7b4f6028 100644 --- a/lib/screens/posts/compose.dart +++ b/lib/posts/compose.dart @@ -1,6 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/post.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts_models/post.dart'; part 'compose.freezed.dart'; part 'compose.g.dart'; diff --git a/lib/screens/posts/compose.freezed.dart b/lib/posts/compose.freezed.dart similarity index 100% rename from lib/screens/posts/compose.freezed.dart rename to lib/posts/compose.freezed.dart diff --git a/lib/screens/posts/compose.g.dart b/lib/posts/compose.g.dart similarity index 100% rename from lib/screens/posts/compose.g.dart rename to lib/posts/compose.g.dart diff --git a/lib/screens/posts/compose_article.dart b/lib/posts/compose_article.dart similarity index 94% rename from lib/screens/posts/compose_article.dart rename to lib/posts/compose_article.dart index cc7ac19e..9f1ffcbd 100644 --- a/lib/screens/posts/compose_article.dart +++ b/lib/posts/compose_article.dart @@ -5,23 +5,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/post.dart'; -import 'package:island/screens/creators/publishers_form.dart'; -import 'package:island/screens/posts/compose.dart'; -import 'package:island/screens/posts/post_detail.dart'; -import 'package:island/services/compose_storage_db.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/markdown.dart'; -import 'package:island/widgets/common/responsive_sidebar.dart'; -import 'package:island/widgets/post/compose_form_fields.dart'; -import 'package:island/widgets/post/compose_shared.dart'; -import 'package:island/widgets/post/compose_attachments.dart'; -import 'package:island/widgets/post/compose_settings_sheet.dart'; -import 'package:island/widgets/post/compose_toolbar.dart'; -import 'package:island/widgets/post/publishers_modal.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts/post_detail.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/creators/creators/publishers_form.dart'; +import 'package:island/posts/compose.dart'; +import 'package:island/posts/compose_storage_db.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/posts/posts_widgets/post/compose_attachments.dart'; +import 'package:island/posts/posts_widgets/post/compose_form_fields.dart'; +import 'package:island/posts/posts_widgets/post/compose_settings_sheet.dart'; +import 'package:island/posts/posts_widgets/post/compose_shared.dart'; +import 'package:island/posts/posts_widgets/post/compose_toolbar.dart'; +import 'package:island/posts/posts_widgets/post/publishers_modal.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/markdown.dart'; +import 'package:island/shared/widgets/responsive_sidebar.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/services/compose_storage_db.dart b/lib/posts/compose_storage_db.dart similarity index 94% rename from lib/services/compose_storage_db.dart rename to lib/posts/compose_storage_db.dart index 5b740e84..dc7e1edf 100644 --- a/lib/services/compose_storage_db.dart +++ b/lib/posts/compose_storage_db.dart @@ -1,8 +1,8 @@ import 'dart:convert'; import 'package:drift/drift.dart'; -import 'package:island/database/drift_db.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/database.dart'; +import 'package:island/data/drift_db.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/core/database.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'compose_storage_db.g.dart'; diff --git a/lib/services/compose_storage_db.g.dart b/lib/posts/compose_storage_db.g.dart similarity index 100% rename from lib/services/compose_storage_db.g.dart rename to lib/posts/compose_storage_db.g.dart diff --git a/lib/pods/post/post_categories.dart b/lib/posts/post/post_categories.dart similarity index 91% rename from lib/pods/post/post_categories.dart rename to lib/posts/post/post_categories.dart index a2b335e4..576c9c80 100644 --- a/lib/pods/post/post_categories.dart +++ b/lib/posts/post/post_categories.dart @@ -2,10 +2,10 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post_category.dart'; -import 'package:island/models/post_tag.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/posts/posts_models/post_category.dart'; +import 'package:island/posts/posts_models/post_tag.dart'; +import 'package:island/core/network.dart'; final postCategoriesProvider = AsyncNotifierProvider.autoDispose< diff --git a/lib/pods/post/post_list.dart b/lib/posts/post/post_list.dart similarity index 97% rename from lib/pods/post/post_list.dart rename to lib/posts/post/post_list.dart index e0013a13..6e9e44ac 100644 --- a/lib/pods/post/post_list.dart +++ b/lib/posts/post/post_list.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/core/network.dart'; part 'post_list.freezed.dart'; diff --git a/lib/pods/post/post_list.freezed.dart b/lib/posts/post/post_list.freezed.dart similarity index 100% rename from lib/pods/post/post_list.freezed.dart rename to lib/posts/post/post_list.freezed.dart diff --git a/lib/posts/posts/compose.dart b/lib/posts/posts/compose.dart new file mode 100644 index 00000000..7b4f6028 --- /dev/null +++ b/lib/posts/posts/compose.dart @@ -0,0 +1,22 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts_models/post.dart'; + +part 'compose.freezed.dart'; +part 'compose.g.dart'; + +@freezed +sealed class PostComposeInitialState with _$PostComposeInitialState { + const factory PostComposeInitialState({ + String? title, + String? description, + String? content, + @Default([]) List attachments, + int? visibility, + SnPost? replyingTo, + SnPost? forwardingTo, + }) = _PostComposeInitialState; + + factory PostComposeInitialState.fromJson(Map json) => + _$PostComposeInitialStateFromJson(json); +} diff --git a/lib/posts/posts/compose.freezed.dart b/lib/posts/posts/compose.freezed.dart new file mode 100644 index 00000000..468425d6 --- /dev/null +++ b/lib/posts/posts/compose.freezed.dart @@ -0,0 +1,343 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'compose.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$PostComposeInitialState { + + String? get title; String? get description; String? get content; List get attachments; int? get visibility; SnPost? get replyingTo; SnPost? get forwardingTo; +/// Create a copy of PostComposeInitialState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PostComposeInitialStateCopyWith get copyWith => _$PostComposeInitialStateCopyWithImpl(this as PostComposeInitialState, _$identity); + + /// Serializes this PostComposeInitialState to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PostComposeInitialState&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.content, content) || other.content == content)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.replyingTo, replyingTo) || other.replyingTo == replyingTo)&&(identical(other.forwardingTo, forwardingTo) || other.forwardingTo == forwardingTo)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,title,description,content,const DeepCollectionEquality().hash(attachments),visibility,replyingTo,forwardingTo); + +@override +String toString() { + return 'PostComposeInitialState(title: $title, description: $description, content: $content, attachments: $attachments, visibility: $visibility, replyingTo: $replyingTo, forwardingTo: $forwardingTo)'; +} + + +} + +/// @nodoc +abstract mixin class $PostComposeInitialStateCopyWith<$Res> { + factory $PostComposeInitialStateCopyWith(PostComposeInitialState value, $Res Function(PostComposeInitialState) _then) = _$PostComposeInitialStateCopyWithImpl; +@useResult +$Res call({ + String? title, String? description, String? content, List attachments, int? visibility, SnPost? replyingTo, SnPost? forwardingTo +}); + + +$SnPostCopyWith<$Res>? get replyingTo;$SnPostCopyWith<$Res>? get forwardingTo; + +} +/// @nodoc +class _$PostComposeInitialStateCopyWithImpl<$Res> + implements $PostComposeInitialStateCopyWith<$Res> { + _$PostComposeInitialStateCopyWithImpl(this._self, this._then); + + final PostComposeInitialState _self; + final $Res Function(PostComposeInitialState) _then; + +/// Create a copy of PostComposeInitialState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? title = freezed,Object? description = freezed,Object? content = freezed,Object? attachments = null,Object? visibility = freezed,Object? replyingTo = freezed,Object? forwardingTo = freezed,}) { + return _then(_self.copyWith( +title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable +as String?,attachments: null == attachments ? _self.attachments : attachments // ignore: cast_nullable_to_non_nullable +as List,visibility: freezed == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable +as int?,replyingTo: freezed == replyingTo ? _self.replyingTo : replyingTo // ignore: cast_nullable_to_non_nullable +as SnPost?,forwardingTo: freezed == forwardingTo ? _self.forwardingTo : forwardingTo // ignore: cast_nullable_to_non_nullable +as SnPost?, + )); +} +/// Create a copy of PostComposeInitialState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnPostCopyWith<$Res>? get replyingTo { + if (_self.replyingTo == null) { + return null; + } + + return $SnPostCopyWith<$Res>(_self.replyingTo!, (value) { + return _then(_self.copyWith(replyingTo: value)); + }); +}/// Create a copy of PostComposeInitialState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnPostCopyWith<$Res>? get forwardingTo { + if (_self.forwardingTo == null) { + return null; + } + + return $SnPostCopyWith<$Res>(_self.forwardingTo!, (value) { + return _then(_self.copyWith(forwardingTo: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [PostComposeInitialState]. +extension PostComposeInitialStatePatterns on PostComposeInitialState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _PostComposeInitialState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PostComposeInitialState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _PostComposeInitialState value) $default,){ +final _that = this; +switch (_that) { +case _PostComposeInitialState(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PostComposeInitialState value)? $default,){ +final _that = this; +switch (_that) { +case _PostComposeInitialState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? title, String? description, String? content, List attachments, int? visibility, SnPost? replyingTo, SnPost? forwardingTo)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PostComposeInitialState() when $default != null: +return $default(_that.title,_that.description,_that.content,_that.attachments,_that.visibility,_that.replyingTo,_that.forwardingTo);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? title, String? description, String? content, List attachments, int? visibility, SnPost? replyingTo, SnPost? forwardingTo) $default,) {final _that = this; +switch (_that) { +case _PostComposeInitialState(): +return $default(_that.title,_that.description,_that.content,_that.attachments,_that.visibility,_that.replyingTo,_that.forwardingTo);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? title, String? description, String? content, List attachments, int? visibility, SnPost? replyingTo, SnPost? forwardingTo)? $default,) {final _that = this; +switch (_that) { +case _PostComposeInitialState() when $default != null: +return $default(_that.title,_that.description,_that.content,_that.attachments,_that.visibility,_that.replyingTo,_that.forwardingTo);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _PostComposeInitialState implements PostComposeInitialState { + const _PostComposeInitialState({this.title, this.description, this.content, final List attachments = const [], this.visibility, this.replyingTo, this.forwardingTo}): _attachments = attachments; + factory _PostComposeInitialState.fromJson(Map json) => _$PostComposeInitialStateFromJson(json); + +@override final String? title; +@override final String? description; +@override final String? content; + final List _attachments; +@override@JsonKey() List get attachments { + if (_attachments is EqualUnmodifiableListView) return _attachments; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_attachments); +} + +@override final int? visibility; +@override final SnPost? replyingTo; +@override final SnPost? forwardingTo; + +/// Create a copy of PostComposeInitialState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PostComposeInitialStateCopyWith<_PostComposeInitialState> get copyWith => __$PostComposeInitialStateCopyWithImpl<_PostComposeInitialState>(this, _$identity); + +@override +Map toJson() { + return _$PostComposeInitialStateToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostComposeInitialState&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.content, content) || other.content == content)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.replyingTo, replyingTo) || other.replyingTo == replyingTo)&&(identical(other.forwardingTo, forwardingTo) || other.forwardingTo == forwardingTo)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,title,description,content,const DeepCollectionEquality().hash(_attachments),visibility,replyingTo,forwardingTo); + +@override +String toString() { + return 'PostComposeInitialState(title: $title, description: $description, content: $content, attachments: $attachments, visibility: $visibility, replyingTo: $replyingTo, forwardingTo: $forwardingTo)'; +} + + +} + +/// @nodoc +abstract mixin class _$PostComposeInitialStateCopyWith<$Res> implements $PostComposeInitialStateCopyWith<$Res> { + factory _$PostComposeInitialStateCopyWith(_PostComposeInitialState value, $Res Function(_PostComposeInitialState) _then) = __$PostComposeInitialStateCopyWithImpl; +@override @useResult +$Res call({ + String? title, String? description, String? content, List attachments, int? visibility, SnPost? replyingTo, SnPost? forwardingTo +}); + + +@override $SnPostCopyWith<$Res>? get replyingTo;@override $SnPostCopyWith<$Res>? get forwardingTo; + +} +/// @nodoc +class __$PostComposeInitialStateCopyWithImpl<$Res> + implements _$PostComposeInitialStateCopyWith<$Res> { + __$PostComposeInitialStateCopyWithImpl(this._self, this._then); + + final _PostComposeInitialState _self; + final $Res Function(_PostComposeInitialState) _then; + +/// Create a copy of PostComposeInitialState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? title = freezed,Object? description = freezed,Object? content = freezed,Object? attachments = null,Object? visibility = freezed,Object? replyingTo = freezed,Object? forwardingTo = freezed,}) { + return _then(_PostComposeInitialState( +title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable +as String?,attachments: null == attachments ? _self._attachments : attachments // ignore: cast_nullable_to_non_nullable +as List,visibility: freezed == visibility ? _self.visibility : visibility // ignore: cast_nullable_to_non_nullable +as int?,replyingTo: freezed == replyingTo ? _self.replyingTo : replyingTo // ignore: cast_nullable_to_non_nullable +as SnPost?,forwardingTo: freezed == forwardingTo ? _self.forwardingTo : forwardingTo // ignore: cast_nullable_to_non_nullable +as SnPost?, + )); +} + +/// Create a copy of PostComposeInitialState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnPostCopyWith<$Res>? get replyingTo { + if (_self.replyingTo == null) { + return null; + } + + return $SnPostCopyWith<$Res>(_self.replyingTo!, (value) { + return _then(_self.copyWith(replyingTo: value)); + }); +}/// Create a copy of PostComposeInitialState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnPostCopyWith<$Res>? get forwardingTo { + if (_self.forwardingTo == null) { + return null; + } + + return $SnPostCopyWith<$Res>(_self.forwardingTo!, (value) { + return _then(_self.copyWith(forwardingTo: value)); + }); +} +} + +// dart format on diff --git a/lib/posts/posts/compose.g.dart b/lib/posts/posts/compose.g.dart new file mode 100644 index 00000000..ca565093 --- /dev/null +++ b/lib/posts/posts/compose.g.dart @@ -0,0 +1,39 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'compose.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_PostComposeInitialState _$PostComposeInitialStateFromJson( + Map json, +) => _PostComposeInitialState( + title: json['title'] as String?, + description: json['description'] as String?, + content: json['content'] as String?, + attachments: + (json['attachments'] as List?) + ?.map((e) => UniversalFile.fromJson(e as Map)) + .toList() ?? + const [], + visibility: (json['visibility'] as num?)?.toInt(), + replyingTo: json['replying_to'] == null + ? null + : SnPost.fromJson(json['replying_to'] as Map), + forwardingTo: json['forwarding_to'] == null + ? null + : SnPost.fromJson(json['forwarding_to'] as Map), +); + +Map _$PostComposeInitialStateToJson( + _PostComposeInitialState instance, +) => { + 'title': instance.title, + 'description': instance.description, + 'content': instance.content, + 'attachments': instance.attachments.map((e) => e.toJson()).toList(), + 'visibility': instance.visibility, + 'replying_to': instance.replyingTo?.toJson(), + 'forwarding_to': instance.forwardingTo?.toJson(), +}; diff --git a/lib/posts/posts/compose_article.dart b/lib/posts/posts/compose_article.dart new file mode 100644 index 00000000..b21bb36c --- /dev/null +++ b/lib/posts/posts/compose_article.dart @@ -0,0 +1,467 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:gap/gap.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/creators/creators/publishers_form.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts/post_detail.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/compose.dart'; +import 'package:island/posts/compose_storage_db.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/posts/posts_widgets/post/compose_attachments.dart'; +import 'package:island/posts/posts_widgets/post/compose_form_fields.dart'; +import 'package:island/posts/posts_widgets/post/compose_settings_sheet.dart'; +import 'package:island/posts/posts_widgets/post/compose_shared.dart'; +import 'package:island/posts/posts_widgets/post/compose_toolbar.dart'; +import 'package:island/posts/posts_widgets/post/publishers_modal.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/markdown.dart'; +import 'package:island/shared/widgets/responsive_sidebar.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:styled_widget/styled_widget.dart'; + +class ArticleEditScreen extends HookConsumerWidget { + final String id; + const ArticleEditScreen({super.key, required this.id}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final post = ref.watch(postProvider(id)); + return post.when( + data: (post) => ArticleComposeScreen(originalPost: post), + loading: () => AppScaffold( + appBar: AppBar(leading: const PageBackButton()), + body: const Center(child: CircularProgressIndicator()), + ), + error: (e, _) => AppScaffold( + appBar: AppBar(leading: const PageBackButton()), + body: Text('Error: $e', textAlign: TextAlign.center), + ), + ); + } +} + +class ArticleComposeScreen extends HookConsumerWidget { + final SnPost? originalPost; + final PostComposeInitialState? initialState; + + const ArticleComposeScreen({super.key, this.originalPost, this.initialState}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + + final publishers = ref.watch(publishersManagedProvider); + final state = useMemoized( + () => ComposeLogic.createState( + originalPost: originalPost, + postType: 1, // Article type + ), + [originalPost], + ); + + // Start auto-save when component mounts + useEffect(() { + Timer? autoSaveTimer; + if (originalPost == null) { + // Only auto-save for new articles, not edits + autoSaveTimer = Timer.periodic(const Duration(seconds: 3), (_) { + ComposeLogic.saveDraftWithoutUpload(ref, state); + }); + } + return () { + // Stop auto-save first to prevent race conditions + state.stopAutoSave(); + // Save final draft before disposing + if (originalPost == null) { + ComposeLogic.saveDraftWithoutUpload(ref, state); + } + ComposeLogic.dispose(state); + autoSaveTimer?.cancel(); + }; + }, [state]); + + final showPreview = useState(false); + final showSidebar = useState(false); + + // Initialize publisher once when data is available + useEffect(() { + if (publishers.value?.isNotEmpty ?? false) { + state.currentPublisher.value = publishers.value!.first; + } + return null; + }, [publishers]); + + // Load initial state if provided (for sharing functionality) + useEffect(() { + if (initialState != null) { + state.titleController.text = initialState!.title ?? ''; + state.descriptionController.text = initialState!.description ?? ''; + state.contentController.text = initialState!.content ?? ''; + if (initialState!.visibility != null) { + state.visibility.value = initialState!.visibility!; + } + if (initialState!.attachments.isNotEmpty) { + state.attachments.value = List.from(initialState!.attachments); + } + } + return null; + }, [initialState]); + + // Load draft if available (only for new articles) + useEffect(() { + if (originalPost == null && initialState == null) { + // Try to load the most recent article draft + final drafts = ref.read(composeStorageProvider); + if (drafts.isNotEmpty) { + final mostRecentDraft = drafts.values.reduce( + (a, b) => + (a.updatedAt ?? DateTime(0)).isAfter(b.updatedAt ?? DateTime(0)) + ? a + : b, + ); + + // Only load if the draft has meaningful content + if (mostRecentDraft.content?.isNotEmpty == true || + mostRecentDraft.title?.isNotEmpty == true) { + state.titleController.text = mostRecentDraft.title ?? ''; + state.descriptionController.text = + mostRecentDraft.description ?? ''; + state.contentController.text = mostRecentDraft.content ?? ''; + state.visibility.value = mostRecentDraft.visibility; + } + } + } + return null; + }, []); + + // Helper methods + Widget buildPreviewPane() { + final widgetItem = SingleChildScrollView( + padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 24), + child: ValueListenableBuilder( + valueListenable: state.titleController, + builder: (context, titleValue, _) { + return ValueListenableBuilder( + valueListenable: state.descriptionController, + builder: (context, descriptionValue, _) { + return ValueListenableBuilder( + valueListenable: state.contentController, + builder: (context, contentValue, _) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (titleValue.text.isNotEmpty) ...[ + Text( + titleValue.text, + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const Gap(20), + ], + if (descriptionValue.text.isNotEmpty) ...[ + Text( + descriptionValue.text, + style: theme.textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurface.withOpacity(0.7), + ), + ), + const Gap(20), + ], + if (contentValue.text.isNotEmpty) + MarkdownTextContent( + content: contentValue.text, + textStyle: theme.textTheme.bodyMedium, + attachments: state.attachments.value + .where((e) => e.isOnCloud) + .map((e) => e.data) + .cast() + .toList(), + ), + ], + ); + }, + ); + }, + ); + }, + ), + ); + + if (isWideScreen(context)) { + return Align(alignment: Alignment.topLeft, child: widgetItem); + } + + return Container( + decoration: BoxDecoration( + color: colorScheme.surface, + border: Border.all(color: colorScheme.outline.withOpacity(0.3)), + borderRadius: BorderRadius.circular(8), + ), + margin: const EdgeInsets.symmetric(vertical: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: colorScheme.surfaceVariant.withOpacity(0.3), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8), + topRight: Radius.circular(8), + ), + ), + child: Row( + children: [ + Icon(Symbols.preview, size: 20), + const Gap(8), + Text('preview'.tr(), style: theme.textTheme.titleMedium), + ], + ), + ), + Expanded(child: widgetItem), + ], + ), + ); + } + + Widget buildEditorPane() { + final editorContent = Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 560), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: ComposeFormFields( + state: state, + showPublisherAvatar: false, + onPublisherTap: () { + showModalBottomSheet( + isScrollControlled: true, + context: context, + builder: (context) => const PublisherModal(), + ).then((value) { + if (value != null) { + state.currentPublisher.value = value; + } + }); + }, + ), + ), + ), + ], + ), + ), + ); + + // Add background color for mobile editor pane + if (!isWideScreen(context)) { + return Container( + decoration: BoxDecoration( + color: colorScheme.surface, + borderRadius: BorderRadius.circular(8), + ), + margin: const EdgeInsets.symmetric(vertical: 8), + child: editorContent, + ); + } + + return editorContent; + } + + return PopScope( + onPopInvoked: (_) { + if (originalPost == null) { + ComposeLogic.saveDraftWithoutUpload(ref, state); + } + }, + child: AppScaffold( + isNoBackground: false, + appBar: AppBar( + leading: const PageBackButton(), + title: ValueListenableBuilder( + valueListenable: state.titleController, + builder: (context, titleValue, _) { + return AnimatedSwitcher( + duration: const Duration(milliseconds: 150), + switchInCurve: Curves.fastEaseInToSlowEaseOut, + switchOutCurve: Curves.fastEaseInToSlowEaseOut, + child: Text( + titleValue.text.isEmpty ? 'postTitle'.tr() : titleValue.text, + key: ValueKey(titleValue.text), + overflow: TextOverflow.ellipsis, + ), + ); + }, + ), + actions: [ + // Info banner for article compose + const SizedBox.shrink(), + IconButton( + icon: ProfilePictureWidget( + file: state.currentPublisher.value?.picture, + radius: 12, + fallbackIcon: state.currentPublisher.value == null + ? Symbols.question_mark + : null, + ), + onPressed: () { + showModalBottomSheet( + isScrollControlled: true, + context: context, + builder: (context) => const PublisherModal(), + ).then((value) { + if (value != null) { + state.currentPublisher.value = value; + } + }); + }, + ), + IconButton( + icon: const Icon(Symbols.tune), + onPressed: () => showSidebar.value = !showSidebar.value, + tooltip: 'sidebar'.tr(), + ), + Tooltip( + message: 'togglePreview'.tr(), + child: IconButton( + icon: Icon( + showPreview.value ? Symbols.preview_off : Symbols.preview, + ), + onPressed: () => showPreview.value = !showPreview.value, + ), + ), + ValueListenableBuilder( + valueListenable: state.submitting, + builder: (context, submitting, _) { + return AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + switchInCurve: Curves.easeOut, + switchOutCurve: Curves.easeIn, + transitionBuilder: + (Widget child, Animation animation) { + return FadeTransition( + opacity: animation, + child: ScaleTransition( + scale: Tween( + begin: 0.8, + end: 1.0, + ).animate(animation), + child: child, + ), + ); + }, + child: submitting + ? SizedBox( + key: const ValueKey('submitting'), + width: 28, + height: 28, + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2.5, + ), + ).center() + : IconButton( + key: const ValueKey('icon'), + onPressed: () => ComposeLogic.performAction( + ref, + state, + context, + originalPost: originalPost, + ), + icon: Icon( + originalPost != null + ? Symbols.edit + : Symbols.upload, + ), + ), + ); + }, + ), + const Gap(8), + ], + ), + body: Column( + children: [ + Expanded( + child: ResponsiveSidebar( + sidebarWidth: 480, + attachmentsContent: ArticleComposeAttachments(state: state), + settingsContent: ComposeSettingsSheet(state: state), + showSidebar: showSidebar, + mainContent: Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 250), + switchInCurve: Curves.easeOutCubic, + switchOutCurve: Curves.easeOutCubic, + transitionBuilder: + (Widget child, Animation animation) { + final isWide = isWideScreen(context); + if (isWide) { + // Desktop: scale animation + return ScaleTransition( + scale: Tween(begin: 0.95, end: 1.0) + .animate( + CurvedAnimation( + parent: animation, + curve: Curves.easeOutCubic, + ), + ), + child: child, + ); + } else { + // Mobile: horizontal slide animation + return SlideTransition( + position: + Tween( + begin: const Offset(0.05, 0), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: animation, + curve: Curves.easeOutCubic, + ), + ), + child: child, + ); + } + }, + child: isWideScreen(context) + ? Row( + spacing: 16, + children: [ + Expanded(child: buildEditorPane()), + if (showPreview.value) + Expanded(child: buildPreviewPane()), + ], + ) + : Container( + key: ValueKey('narrow-${showPreview.value}'), + child: showPreview.value + ? buildPreviewPane() + : buildEditorPane(), + ), + ), + ), + ), + ), + + // Bottom toolbar + ComposeToolbar(state: state, originalPost: originalPost), + ], + ), + ), + ); + } +} diff --git a/lib/screens/posts/post_categories_list.dart b/lib/posts/posts/post_categories_list.dart similarity index 95% rename from lib/screens/posts/post_categories_list.dart rename to lib/posts/posts/post_categories_list.dart index a0bf640e..a5fa7b06 100644 --- a/lib/screens/posts/post_categories_list.dart +++ b/lib/posts/posts/post_categories_list.dart @@ -2,9 +2,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/post/post_categories.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/posts/post/post_categories.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; class PostCategoriesListScreen extends HookConsumerWidget { diff --git a/lib/screens/posts/post_category_detail.dart b/lib/posts/posts/post_category_detail.dart similarity index 95% rename from lib/screens/posts/post_category_detail.dart rename to lib/posts/posts/post_category_detail.dart index e67d7328..bd761a1a 100644 --- a/lib/screens/posts/post_category_detail.dart +++ b/lib/posts/posts/post_category_detail.dart @@ -2,13 +2,13 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post_category.dart'; -import 'package:island/models/post_tag.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/post/post_list.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/post/post_list.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/posts/post/post_list.dart'; +import 'package:island/posts/posts_models/post_category.dart'; +import 'package:island/posts/posts_models/post_tag.dart'; +import 'package:island/core/network.dart'; +import 'package:island/posts/posts_widgets/post/post_list.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -223,7 +223,6 @@ class PostCategoryDetailScreen extends HookConsumerWidget { categories: isCategory ? [slug] : null, tags: isCategory ? null : [slug], ), - maxWidth: 540 + 16, ), SliverGap(MediaQuery.of(context).padding.bottom + 8), diff --git a/lib/screens/posts/post_category_detail.g.dart b/lib/posts/posts/post_category_detail.g.dart similarity index 100% rename from lib/screens/posts/post_category_detail.g.dart rename to lib/posts/posts/post_category_detail.g.dart diff --git a/lib/screens/posts/post_detail.dart b/lib/posts/posts/post_detail.dart similarity index 93% rename from lib/screens/posts/post_detail.dart rename to lib/posts/posts/post_detail.dart index c007cb7b..ee1f80a6 100644 --- a/lib/screens/posts/post_detail.dart +++ b/lib/posts/posts/post_detail.dart @@ -5,28 +5,28 @@ import 'package:flutter/services.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/screens/posts/compose.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_file_collection.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; -import 'package:island/widgets/post/post_award_sheet.dart'; -import 'package:island/widgets/post/post_item.dart'; -import 'package:island/widgets/post/post_award_history_sheet.dart'; -import 'package:island/widgets/post/post_pin_sheet.dart'; -import 'package:island/widgets/post/post_quick_reply.dart'; -import 'package:island/widgets/post/compose_sheet.dart'; -import 'package:island/widgets/post/post_replies.dart'; -import 'package:island/widgets/post/post_shared.dart'; -import 'package:island/widgets/response.dart'; -import 'package:island/utils/share_utils.dart'; -import 'package:island/widgets/safety/abuse_report_helper.dart'; -import 'package:island/widgets/share/share_sheet.dart'; -import 'package:island/screens/thought/think_sheet.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/posts/compose.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/posts/posts_widgets/post/post_award_history_sheet.dart'; +import 'package:island/posts/posts_widgets/post/post_award_sheet.dart'; +import 'package:island/posts/posts_widgets/post/post_item.dart'; +import 'package:island/posts/posts_widgets/post/post_pin_sheet.dart'; +import 'package:island/posts/posts_widgets/post/post_quick_reply.dart'; +import 'package:island/posts/posts_widgets/post/post_replies.dart'; +import 'package:island/posts/posts_widgets/post/post_shared.dart'; +import 'package:island/reports/reports_widgets/safety/abuse_report_helper.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/core/widgets/content/cloud_file_collection.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; +import 'package:island/posts/posts_widgets/compose_sheet.dart'; +import 'package:island/shared/widgets/response.dart'; +import 'package:island/core/utils/share_utils.dart'; +import 'package:island/core/widgets/share/share_sheet.dart'; +import 'package:island/thought/thought/think_sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/posts/post_detail.g.dart b/lib/posts/posts/post_detail.g.dart similarity index 100% rename from lib/screens/posts/post_detail.g.dart rename to lib/posts/posts/post_detail.g.dart diff --git a/lib/screens/posts/publisher_profile.dart b/lib/posts/posts/publisher_profile.dart similarity index 95% rename from lib/screens/posts/publisher_profile.dart rename to lib/posts/posts/publisher_profile.dart index 3ce5af45..a17503dc 100644 --- a/lib/screens/posts/publisher_profile.dart +++ b/lib/posts/posts/publisher_profile.dart @@ -6,28 +6,28 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/heatmap.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/post/post_list.dart'; -import 'package:island/services/color.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/account/account_name.dart'; -import 'package:island/widgets/account/badge.dart'; -import 'package:island/widgets/account/status.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/markdown.dart'; -import 'package:island/widgets/post/post_list.dart'; -import 'package:island/widgets/post/post_item.dart'; -import 'package:island/widgets/activity_heatmap.dart'; -import 'package:island/widgets/post/filters/post_filter.dart'; +import 'package:island/accounts/accounts_widgets/account/account_name.dart'; +import 'package:island/accounts/accounts_widgets/account/badge.dart'; +import 'package:island/accounts/accounts_widgets/account/status.dart'; +import 'package:island/posts/post/post_list.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/posts/posts_models/heatmap.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/posts/posts_widgets/post/filters/post_filter.dart'; +import 'package:island/posts/posts_widgets/post/post_item.dart'; +import 'package:island/posts/posts_widgets/post/post_list.dart'; +import 'package:island/core/services/color.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/markdown.dart'; +import 'package:island/posts/activity_heatmap.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:island/services/color_extraction.dart'; +import 'package:island/core/services/color_extraction.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/posts/publisher_profile.g.dart b/lib/posts/posts/publisher_profile.g.dart similarity index 100% rename from lib/screens/posts/publisher_profile.g.dart rename to lib/posts/posts/publisher_profile.g.dart diff --git a/lib/models/embed.dart b/lib/posts/posts_models/embed.dart similarity index 100% rename from lib/models/embed.dart rename to lib/posts/posts_models/embed.dart diff --git a/lib/models/embed.freezed.dart b/lib/posts/posts_models/embed.freezed.dart similarity index 100% rename from lib/models/embed.freezed.dart rename to lib/posts/posts_models/embed.freezed.dart diff --git a/lib/models/embed.g.dart b/lib/posts/posts_models/embed.g.dart similarity index 100% rename from lib/models/embed.g.dart rename to lib/posts/posts_models/embed.g.dart diff --git a/lib/models/heatmap.dart b/lib/posts/posts_models/heatmap.dart similarity index 100% rename from lib/models/heatmap.dart rename to lib/posts/posts_models/heatmap.dart diff --git a/lib/models/heatmap.freezed.dart b/lib/posts/posts_models/heatmap.freezed.dart similarity index 100% rename from lib/models/heatmap.freezed.dart rename to lib/posts/posts_models/heatmap.freezed.dart diff --git a/lib/models/heatmap.g.dart b/lib/posts/posts_models/heatmap.g.dart similarity index 100% rename from lib/models/heatmap.g.dart rename to lib/posts/posts_models/heatmap.g.dart diff --git a/lib/models/poll.dart b/lib/posts/posts_models/poll.dart similarity index 96% rename from lib/models/poll.dart rename to lib/posts/posts_models/poll.dart index 8f11ef2e..62c250f1 100644 --- a/lib/models/poll.dart +++ b/lib/posts/posts_models/poll.dart @@ -1,6 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/publisher.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/posts/posts_models/publisher.dart'; part 'poll.freezed.dart'; part 'poll.g.dart'; diff --git a/lib/models/poll.freezed.dart b/lib/posts/posts_models/poll.freezed.dart similarity index 100% rename from lib/models/poll.freezed.dart rename to lib/posts/posts_models/poll.freezed.dart diff --git a/lib/models/poll.g.dart b/lib/posts/posts_models/poll.g.dart similarity index 100% rename from lib/models/poll.g.dart rename to lib/posts/posts_models/poll.g.dart diff --git a/lib/models/post.dart b/lib/posts/posts_models/post.dart similarity index 93% rename from lib/models/post.dart rename to lib/posts/posts_models/post.dart index 8490c57d..eb2ea482 100644 --- a/lib/models/post.dart +++ b/lib/posts/posts_models/post.dart @@ -1,11 +1,11 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/activitypub.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/post_category.dart'; -import 'package:island/models/post_tag.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/models/realm.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/models/activitypub.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts_models/post_category.dart'; +import 'package:island/posts/posts_models/post_tag.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/realms/realms_models/realm.dart'; part 'post.freezed.dart'; part 'post.g.dart'; diff --git a/lib/models/post.freezed.dart b/lib/posts/posts_models/post.freezed.dart similarity index 100% rename from lib/models/post.freezed.dart rename to lib/posts/posts_models/post.freezed.dart diff --git a/lib/models/post.g.dart b/lib/posts/posts_models/post.g.dart similarity index 100% rename from lib/models/post.g.dart rename to lib/posts/posts_models/post.g.dart diff --git a/lib/models/post_category.dart b/lib/posts/posts_models/post_category.dart similarity index 89% rename from lib/models/post_category.dart rename to lib/posts/posts_models/post_category.dart index 0c453f55..9103459d 100644 --- a/lib/models/post_category.dart +++ b/lib/posts/posts_models/post_category.dart @@ -1,8 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/post.dart'; -import 'package:island/models/post_tag.dart'; -import 'package:island/utils/text.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/posts_models/post_tag.dart'; +import 'package:island/core/utils/text.dart'; part 'post_category.freezed.dart'; part 'post_category.g.dart'; diff --git a/lib/models/post_category.freezed.dart b/lib/posts/posts_models/post_category.freezed.dart similarity index 100% rename from lib/models/post_category.freezed.dart rename to lib/posts/posts_models/post_category.freezed.dart diff --git a/lib/models/post_category.g.dart b/lib/posts/posts_models/post_category.g.dart similarity index 100% rename from lib/models/post_category.g.dart rename to lib/posts/posts_models/post_category.g.dart diff --git a/lib/models/post_tag.dart b/lib/posts/posts_models/post_tag.dart similarity index 89% rename from lib/models/post_tag.dart rename to lib/posts/posts_models/post_tag.dart index f4b899c3..d15ecef9 100644 --- a/lib/models/post_tag.dart +++ b/lib/posts/posts_models/post_tag.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/post.dart'; +import 'package:island/posts/posts_models/post.dart'; part 'post_tag.freezed.dart'; part 'post_tag.g.dart'; diff --git a/lib/models/post_tag.freezed.dart b/lib/posts/posts_models/post_tag.freezed.dart similarity index 100% rename from lib/models/post_tag.freezed.dart rename to lib/posts/posts_models/post_tag.freezed.dart diff --git a/lib/models/post_tag.g.dart b/lib/posts/posts_models/post_tag.g.dart similarity index 100% rename from lib/models/post_tag.g.dart rename to lib/posts/posts_models/post_tag.g.dart diff --git a/lib/models/publisher.dart b/lib/posts/posts_models/publisher.dart similarity index 91% rename from lib/models/publisher.dart rename to lib/posts/posts_models/publisher.dart index 3c695528..dc552520 100644 --- a/lib/models/publisher.dart +++ b/lib/posts/posts_models/publisher.dart @@ -1,6 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/account.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/accounts/accounts_models/account.dart'; part 'publisher.freezed.dart'; part 'publisher.g.dart'; diff --git a/lib/models/publisher.freezed.dart b/lib/posts/posts_models/publisher.freezed.dart similarity index 100% rename from lib/models/publisher.freezed.dart rename to lib/posts/posts_models/publisher.freezed.dart diff --git a/lib/models/publisher.g.dart b/lib/posts/posts_models/publisher.g.dart similarity index 100% rename from lib/models/publisher.g.dart rename to lib/posts/posts_models/publisher.g.dart diff --git a/lib/pods/timeline.dart b/lib/posts/posts_pod.dart similarity index 91% rename from lib/pods/timeline.dart rename to lib/posts/posts_pod.dart index 9899cdd3..263aa045 100644 --- a/lib/pods/timeline.dart +++ b/lib/posts/posts_pod.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/activity.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; +import 'package:island/core/models/activity.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/pagination/pagination.dart'; final activityListProvider = AsyncNotifierProvider.autoDispose( ActivityListNotifier.new, @@ -76,7 +76,9 @@ class ActivityListNotifier // Check for duplicate items by id final existingItemIds = state.value?.items.map((e) => e.id).toSet() ?? {}; - final uniqueItems = items.where((item) => !existingItemIds.contains(item.id)).toList(); + final uniqueItems = items + .where((item) => !existingItemIds.contains(item.id)) + .toList(); // If no new items and we haven't reached max retry attempts, adjust cursor and retry if (uniqueItems.isEmpty && retryCount < maxRetryAttempts) { diff --git a/lib/widgets/post/compose_sheet.dart b/lib/posts/posts_widgets/compose_sheet.dart similarity index 94% rename from lib/widgets/post/compose_sheet.dart rename to lib/posts/posts_widgets/compose_sheet.dart index 43b77679..71f992ab 100644 --- a/lib/widgets/post/compose_sheet.dart +++ b/lib/posts/posts_widgets/compose_sheet.dart @@ -6,16 +6,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/post.dart'; -import 'package:island/screens/posts/compose.dart'; -import 'package:island/screens/posts/post_detail.dart'; -import 'package:island/services/compose_storage_db.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/post/compose_card.dart'; -import 'package:island/widgets/post/compose_shared.dart'; -import 'package:island/widgets/post/compose_state_utils.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts/post_detail.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/compose.dart'; +import 'package:island/posts/compose_storage_db.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/posts/posts_widgets/post/compose_card.dart'; +import 'package:island/posts/posts_widgets/post/compose_shared.dart'; +import 'package:island/posts/posts_widgets/post/compose_state_utils.dart'; import 'package:material_symbols_icons/symbols.dart'; /// A dialog that wraps PostComposeCard for easy use in dialogs. diff --git a/lib/widgets/post/article_sidebar_panel.dart b/lib/posts/posts_widgets/post/article_sidebar_panel.dart similarity index 100% rename from lib/widgets/post/article_sidebar_panel.dart rename to lib/posts/posts_widgets/post/article_sidebar_panel.dart diff --git a/lib/widgets/post/compose_attachments.dart b/lib/posts/posts_widgets/post/compose_attachments.dart similarity index 79% rename from lib/widgets/post/compose_attachments.dart rename to lib/posts/posts_widgets/post/compose_attachments.dart index f4be87e8..0d51baff 100644 --- a/lib/widgets/post/compose_attachments.dart +++ b/lib/posts/posts_widgets/post/compose_attachments.dart @@ -3,12 +3,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/services/file_uploader.dart'; -import 'package:island/widgets/attachment_uploader.dart'; -import 'package:island/widgets/content/attachment_preview.dart'; -import 'package:island/widgets/post/compose_shared.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/posts/posts_widgets/post/compose_shared.dart'; +import 'package:island/shared/widgets/attachment_uploader.dart'; +import 'package:island/core/widgets/content/attachment_preview.dart'; import 'package:material_symbols_icons/symbols.dart'; /// A reusable widget for displaying attachments in compose screens. @@ -117,7 +117,10 @@ class ArticleComposeAttachments extends HookConsumerWidget { this.padding, }); - Future _handleDroppedFiles(DropDoneDetails details, ComposeState state) async { + Future _handleDroppedFiles( + DropDoneDetails details, + ComposeState state, + ) async { final newFiles = []; for (final xfile in details.files) { @@ -163,15 +166,19 @@ class ArticleComposeAttachments extends HookConsumerWidget { child: AnimatedContainer( duration: const Duration(milliseconds: 200), curve: Curves.easeOut, - decoration: isDragging.value ? BoxDecoration( - border: Border.all( - color: Theme.of(context).colorScheme.primary, - width: 2, - ), - borderRadius: BorderRadius.circular(12), - ) : null, + decoration: isDragging.value + ? BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.primary, + width: 2, + ), + borderRadius: BorderRadius.circular(12), + ) + : null, child: Padding( - padding: isDragging.value ? const EdgeInsets.all(8) : EdgeInsets.zero, + padding: isDragging.value + ? const EdgeInsets.all(8) + : EdgeInsets.zero, child: attachments.isEmpty ? AnimatedContainer( duration: const Duration(milliseconds: 200), @@ -180,13 +187,13 @@ class ArticleComposeAttachments extends HookConsumerWidget { height: 200, decoration: BoxDecoration( color: Theme.of(context) - .colorScheme - .surfaceContainerHighest - .withOpacity(0.3), + .colorScheme + .surfaceContainerHighest + .withOpacity(0.3), border: Border.all( color: Theme.of( - context, - ).colorScheme.outline.withOpacity(0.5), + context, + ).colorScheme.outline.withOpacity(0.5), width: 1, ), borderRadius: BorderRadius.circular(12), @@ -204,7 +211,9 @@ class ArticleComposeAttachments extends HookConsumerWidget { const SizedBox(height: 16), Text( 'dropFilesHere', - style: Theme.of(context).textTheme.titleMedium + style: Theme.of(context) + .textTheme + .titleMedium ?.copyWith( color: Theme.of( context, @@ -214,7 +223,9 @@ class ArticleComposeAttachments extends HookConsumerWidget { const SizedBox(height: 8), Text( 'dragAndDropToAttach', - style: Theme.of(context).textTheme.bodySmall + style: Theme.of(context) + .textTheme + .bodySmall ?.copyWith( color: Theme.of(context) .colorScheme @@ -232,15 +243,24 @@ class ArticleComposeAttachments extends HookConsumerWidget { runSpacing: 8, spacing: 8, children: [ - for (var idx = 0; idx < attachments.length; idx++) + for ( + var idx = 0; + idx < attachments.length; + idx++ + ) _AnimatedAttachmentItem( index: idx, item: attachments[idx], progress: progressMap[idx], - isUploading: progressMap.containsKey(idx), + isUploading: progressMap.containsKey( + idx, + ), thumbnailId: thumbnailId, onSetThumbnail: (id) => - ComposeLogic.setThumbnail(state, id), + ComposeLogic.setThumbnail( + state, + id, + ), onRequestUpload: () async { final config = await showModalBottomSheet< @@ -249,11 +269,12 @@ class ArticleComposeAttachments extends HookConsumerWidget { context: context, isScrollControlled: true, useRootNavigator: true, - builder: (context) => AttachmentUploaderSheet( - ref: ref, - state: state, - index: idx, - ), + builder: (context) => + AttachmentUploaderSheet( + ref: ref, + state: state, + index: idx, + ), ); if (config != null) { await ComposeLogic.uploadAttachment( @@ -265,11 +286,23 @@ class ArticleComposeAttachments extends HookConsumerWidget { } }, onUpdate: (value) => - ComposeLogic.updateAttachment(state, value, idx), + ComposeLogic.updateAttachment( + state, + value, + idx, + ), onDelete: () => - ComposeLogic.deleteAttachment(ref, state, idx), + ComposeLogic.deleteAttachment( + ref, + state, + idx, + ), onInsert: () => - ComposeLogic.insertAttachment(ref, state, idx), + ComposeLogic.insertAttachment( + ref, + state, + idx, + ), ), ], ); @@ -359,4 +392,4 @@ class _AnimatedAttachmentItem extends HookWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/widgets/post/compose_card.dart b/lib/posts/posts_widgets/post/compose_card.dart similarity index 93% rename from lib/widgets/post/compose_card.dart rename to lib/posts/posts_widgets/post/compose_card.dart index 32eb45e5..39bd5e57 100644 --- a/lib/widgets/post/compose_card.dart +++ b/lib/posts/posts_widgets/post/compose_card.dart @@ -5,22 +5,22 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/post.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/screens/creators/publishers_form.dart'; -import 'package:island/screens/posts/compose.dart'; -import 'package:island/services/compose_storage_db.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/post/compose_attachments.dart'; -import 'package:island/widgets/post/compose_form_fields.dart'; -import 'package:island/widgets/post/compose_info_banner.dart'; -import 'package:island/widgets/post/compose_shared.dart'; -import 'package:island/widgets/post/compose_state_utils.dart'; -import 'package:island/widgets/post/compose_toolbar.dart'; -import 'package:island/widgets/post/post_item.dart'; -import 'package:island/widgets/post/publishers_modal.dart'; +import 'package:island/creators/creators/publishers_form.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/posts/compose.dart'; +import 'package:island/posts/compose_storage_db.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/posts/posts_widgets/post/compose_attachments.dart'; +import 'package:island/posts/posts_widgets/post/compose_form_fields.dart'; +import 'package:island/posts/posts_widgets/post/compose_info_banner.dart'; +import 'package:island/posts/posts_widgets/post/compose_shared.dart'; +import 'package:island/posts/posts_widgets/post/compose_state_utils.dart'; +import 'package:island/posts/posts_widgets/post/compose_toolbar.dart'; +import 'package:island/posts/posts_widgets/post/post_item.dart'; +import 'package:island/posts/posts_widgets/post/publishers_modal.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/post/compose_dialog.dart b/lib/posts/posts_widgets/post/compose_dialog.dart similarity index 78% rename from lib/widgets/post/compose_dialog.dart rename to lib/posts/posts_widgets/post/compose_dialog.dart index 82bf5e19..2401cd81 100644 --- a/lib/widgets/post/compose_dialog.dart +++ b/lib/posts/posts_widgets/post/compose_dialog.dart @@ -2,15 +2,15 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/post.dart'; -import 'package:island/screens/posts/compose.dart'; -import 'package:island/services/compose_storage_db.dart'; -import 'package:island/services/event_bus.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/post/compose_card.dart'; -import 'package:island/widgets/post/compose_shared.dart'; -import 'package:island/widgets/post/compose_state_utils.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/compose.dart'; +import 'package:island/posts/compose_storage_db.dart'; +import 'package:island/core/services/event_bus.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/posts/posts_widgets/post/compose_card.dart'; +import 'package:island/posts/posts_widgets/post/compose_shared.dart'; +import 'package:island/posts/posts_widgets/post/compose_state_utils.dart'; import 'package:material_symbols_icons/symbols.dart'; /// A dialog that wraps PostComposeCard for easy use in dialogs. @@ -36,12 +36,11 @@ class PostComposeDialog extends HookConsumerWidget { context: context, isScrollControlled: true, useRootNavigator: true, - builder: - (context) => PostComposeDialog( - originalPost: originalPost, - initialState: initialState, - isBottomSheet: true, - ), + builder: (context) => PostComposeDialog( + originalPost: originalPost, + initialState: initialState, + isBottomSheet: true, + ), ); } @@ -130,16 +129,15 @@ class PostComposeDialog extends HookConsumerWidget { IconButton( onPressed: (state.submitting.value || state.currentPublisher.value == null) - ? null - : performSubmit, - icon: - state.submitting.value - ? SizedBox( - width: 24, - height: 24, - child: const CircularProgressIndicator(strokeWidth: 2), - ) - : Icon(originalPost != null ? Symbols.edit : Symbols.upload), + ? null + : performSubmit, + icon: state.submitting.value + ? SizedBox( + width: 24, + height: 24, + child: const CircularProgressIndicator(strokeWidth: 2), + ) + : Icon(originalPost != null ? Symbols.edit : Symbols.upload), tooltip: originalPost != null ? 'postUpdate'.tr() : 'postPublish'.tr(), ), ]; @@ -173,29 +171,28 @@ class PostComposeDialog extends HookConsumerWidget { final restore = await showDialog( context: ref.context, useRootNavigator: true, - builder: - (context) => AlertDialog( - title: Text('restoreDraftTitle'.tr()), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('restoreDraftMessage'.tr()), - const SizedBox(height: 16), - _buildCompactDraftPreview(context, latestDraft), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(false), - child: Text('no'.tr()), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(true), - child: Text('yes'.tr()), - ), - ], + builder: (context) => AlertDialog( + title: Text('restoreDraftTitle'.tr()), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('restoreDraftMessage'.tr()), + const SizedBox(height: 16), + _buildCompactDraftPreview(context, latestDraft), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text('no'.tr()), ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text('yes'.tr()), + ), + ], + ), ); if (restore == true) { // Delete the old draft @@ -207,10 +204,9 @@ class PostComposeDialog extends HookConsumerWidget { description: latestDraft.description, content: latestDraft.content, visibility: latestDraft.visibility, - attachments: - latestDraft.attachments - .map((e) => UniversalFile.fromAttachment(e)) - .toList(), + attachments: latestDraft.attachments + .map((e) => UniversalFile.fromAttachment(e)) + .toList(), ); } } diff --git a/lib/widgets/post/compose_embed_sheet.dart b/lib/posts/posts_widgets/post/compose_embed_sheet.dart similarity index 86% rename from lib/widgets/post/compose_embed_sheet.dart rename to lib/posts/posts_widgets/post/compose_embed_sheet.dart index a9da1c23..6a6c57d6 100644 --- a/lib/widgets/post/compose_embed_sheet.dart +++ b/lib/posts/posts_widgets/post/compose_embed_sheet.dart @@ -5,10 +5,10 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/post/compose_shared.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/posts_widgets/post/compose_shared.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -61,10 +61,9 @@ class ComposeEmbedSheet extends HookConsumerWidget { return; } - final aspectRatio = - aspectRatioController.text.trim().isNotEmpty - ? double.tryParse(aspectRatioController.text.trim()) - : null; + final aspectRatio = aspectRatioController.text.trim().isNotEmpty + ? double.tryParse(aspectRatioController.text.trim()) + : null; final embedView = SnPostEmbedView( uri: uri, @@ -105,28 +104,27 @@ class ComposeEmbedSheet extends HookConsumerWidget { void deleteEmbed(BuildContext context) { showDialog( context: context, - builder: - (dialogContext) => AlertDialog( - title: Text('deleteEmbed').tr(), - content: Text('deleteEmbedConfirm').tr(), - actions: [ - TextButton( - onPressed: () => Navigator.of(dialogContext).pop(), - child: Text('cancel').tr(), - ), - TextButton( - onPressed: () { - ComposeLogic.deleteEmbedView(state); - clearForm(); - Navigator.of(dialogContext).pop(); - }, - style: TextButton.styleFrom( - foregroundColor: Theme.of(context).colorScheme.error, - ), - child: Text('delete').tr(), - ), - ], + builder: (dialogContext) => AlertDialog( + title: Text('deleteEmbed').tr(), + content: Text('deleteEmbedConfirm').tr(), + actions: [ + TextButton( + onPressed: () => Navigator.of(dialogContext).pop(), + child: Text('cancel').tr(), ), + TextButton( + onPressed: () { + ComposeLogic.deleteEmbedView(state); + clearForm(); + Navigator.of(dialogContext).pop(); + }, + style: TextButton.styleFrom( + foregroundColor: Theme.of(context).colorScheme.error, + ), + child: Text('delete').tr(), + ), + ], + ), ); } @@ -160,7 +158,10 @@ class ComposeEmbedSheet extends HookConsumerWidget { // Tab bar TabBar( controller: tabController, - tabs: [Tab(text: 'auto'.tr()), Tab(text: 'manual'.tr())], + tabs: [ + Tab(text: 'auto'.tr()), + Tab(text: 'manual'.tr()), + ], ), // Content area @@ -251,15 +252,14 @@ class ComposeEmbedSheet extends HookConsumerWidget { menuItemStyleData: MenuItemStyleData( padding: EdgeInsets.zero, ), - items: - PostEmbedViewRenderer.values.map((renderer) { - return DropdownMenuItem( - value: renderer, - child: Text( - renderer.name, - ).tr().padding(horizontal: 20), - ); - }).toList(), + items: PostEmbedViewRenderer.values.map((renderer) { + return DropdownMenuItem( + value: renderer, + child: Text( + renderer.name, + ).tr().padding(horizontal: 20), + ); + }).toList(), onChanged: (value) { if (value != null) { selectedRenderer.value = value; @@ -277,10 +277,9 @@ class ComposeEmbedSheet extends HookConsumerWidget { const Gap(8), Card( margin: EdgeInsets.zero, - color: - Theme.of( - context, - ).colorScheme.surfaceContainerHigh, + color: Theme.of( + context, + ).colorScheme.surfaceContainerHigh, child: Padding( padding: const EdgeInsets.only( left: 16, @@ -328,7 +327,7 @@ class ComposeEmbedSheet extends HookConsumerWidget { Text( currentEmbedView.aspectRatio != null ? currentEmbedView.aspectRatio! - .toStringAsFixed(2) + .toStringAsFixed(2) : 'notSet'.tr(), style: theme.textTheme.bodyMedium, ), diff --git a/lib/widgets/post/compose_form_fields.dart b/lib/posts/posts_widgets/post/compose_form_fields.dart similarity index 95% rename from lib/widgets/post/compose_form_fields.dart rename to lib/posts/posts_widgets/post/compose_form_fields.dart index 6d081b1f..d1ed3695 100644 --- a/lib/widgets/post/compose_form_fields.dart +++ b/lib/posts/posts_widgets/post/compose_form_fields.dart @@ -2,15 +2,15 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/autocomplete_response.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/models/sticker.dart'; -import 'package:island/services/autocomplete_service.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/post/compose_shared.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/discovery/discovery_models/autocomplete_response.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/posts/posts_widgets/post/compose_shared.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/stickers/stickers_models/sticker.dart'; +import 'package:island/discovery/discovery_service.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; /// A reusable widget for the form fields in compose screens. /// Includes title, description, and content text fields. diff --git a/lib/widgets/post/compose_fund.dart b/lib/posts/posts_widgets/post/compose_fund.dart similarity index 98% rename from lib/widgets/post/compose_fund.dart rename to lib/posts/posts_widgets/post/compose_fund.dart index 9a1c98ce..ec8263d5 100644 --- a/lib/widgets/post/compose_fund.dart +++ b/lib/posts/posts_widgets/post/compose_fund.dart @@ -4,12 +4,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/wallet.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/wallet.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/payment/payment_overlay.dart'; +import 'package:island/wallet/wallet.dart'; +import 'package:island/wallet/wallet_models/wallet.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/core/widgets/payment/payment_overlay.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/post/compose_info_banner.dart b/lib/posts/posts_widgets/post/compose_info_banner.dart similarity index 98% rename from lib/widgets/post/compose_info_banner.dart rename to lib/posts/posts_widgets/post/compose_info_banner.dart index bad1011b..5c4e0d7b 100644 --- a/lib/widgets/post/compose_info_banner.dart +++ b/lib/posts/posts_widgets/post/compose_info_banner.dart @@ -1,8 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:island/models/post.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/post/compose_link_attachments.dart b/lib/posts/posts_widgets/post/compose_link_attachments.dart similarity index 95% rename from lib/widgets/post/compose_link_attachments.dart rename to lib/posts/posts_widgets/post/compose_link_attachments.dart index 2bfe61de..64219f5a 100644 --- a/lib/widgets/post/compose_link_attachments.dart +++ b/lib/posts/posts_widgets/post/compose_link_attachments.dart @@ -3,12 +3,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/core/network.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher_string.dart'; diff --git a/lib/widgets/post/compose_poll.dart b/lib/posts/posts_widgets/post/compose_poll.dart similarity index 62% rename from lib/widgets/post/compose_poll.dart rename to lib/posts/posts_widgets/post/compose_poll.dart index cdb1a461..c7b0ec5c 100644 --- a/lib/widgets/post/compose_poll.dart +++ b/lib/posts/posts_widgets/post/compose_poll.dart @@ -3,16 +3,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/poll.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/screens/creators/poll/poll_list.dart'; -import 'package:island/screens/poll/poll_editor.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/creators/creators/poll/poll_list.dart'; +import 'package:island/polls/poll/poll_editor.dart'; +import 'package:island/posts/posts_models/poll.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/posts/posts_widgets/post/publishers_modal.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:island/widgets/post/publishers_modal.dart'; /// Bottom sheet for selecting or creating a poll. Returns SnPoll via Navigator.pop. class ComposePollSheet extends HookConsumerWidget { @@ -87,20 +87,19 @@ class ComposePollSheet extends HookConsumerWidget { ? 'publisherHint'.tr() : '@${selectedPublisher.value?.name}', ), - leading: - selectedPublisher.value == null - ? const Icon(Symbols.account_circle) - : ProfilePictureWidget( - file: selectedPublisher.value?.picture, - ), + leading: selectedPublisher.value == null + ? const Icon(Symbols.account_circle) + : ProfilePictureWidget( + file: selectedPublisher.value?.picture, + ), trailing: const Icon(Symbols.chevron_right), onTap: () async { final picked = await showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => const PublisherModal(), + builder: (context) => + const PublisherModal(), ); if (picked != null) { try { @@ -129,53 +128,51 @@ class ComposePollSheet extends HookConsumerWidget { Align( alignment: Alignment.centerRight, child: FilledButton.icon( - icon: - isPushing.value - ? const SizedBox( - width: 18, - height: 18, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white, - ), - ) - : const Icon(Symbols.add_circle), + icon: isPushing.value + ? const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Icon(Symbols.add_circle), label: Text('create'.tr()), - onPressed: - isPushing.value - ? null - : () async { - if (pub == null) { - errorText.value = - 'publisherCannotBeEmpty'.tr(); - return; - } - errorText.value = null; + onPressed: isPushing.value + ? null + : () async { + if (pub == null) { + errorText.value = 'publisherCannotBeEmpty' + .tr(); + return; + } + errorText.value = null; - isPushing.value = true; - // Show modal bottom sheet with poll editor and await result - final result = - await showModalBottomSheet( - context: context, - isScrollControlled: true, - isDismissible: false, - enableDrag: false, - builder: - (context) => PollEditorScreen( - initialPublisher: pub?.name, - ), - ); + isPushing.value = true; + // Show modal bottom sheet with poll editor and await result + final result = + await showModalBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: false, + enableDrag: false, + builder: (context) => + PollEditorScreen( + initialPublisher: pub?.name, + ), + ); - if (result == null) { - isPushing.value = false; - return; - } + if (result == null) { + isPushing.value = false; + return; + } - if (!context.mounted) return; + if (!context.mounted) return; - // Return created poll to caller of this bottom sheet - Navigator.of(context).pop(result); - }, + // Return created poll to caller of this bottom sheet + Navigator.of(context).pop(result); + }, ), ), ], diff --git a/lib/widgets/post/compose_recorder.dart b/lib/posts/posts_widgets/post/compose_recorder.dart similarity index 91% rename from lib/widgets/post/compose_recorder.dart rename to lib/posts/posts_widgets/post/compose_recorder.dart index 02e167f7..1df294b3 100644 --- a/lib/widgets/post/compose_recorder.dart +++ b/lib/posts/posts_widgets/post/compose_recorder.dart @@ -6,10 +6,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/services/time.dart'; +import 'package:island/core/services/time.dart'; import 'package:island/talker.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:path_provider/path_provider.dart'; import 'package:record/record.dart' hide Amplitude; @@ -130,14 +130,9 @@ class ComposeRecorder extends HookConsumerWidget { IconButton.filled( onPressed: recording.value ? stopRecord : startRecord, iconSize: 32, - icon: - recording.value - ? const Icon(Symbols.stop, fill: 1, color: Colors.white) - : const Icon( - Symbols.play_arrow, - fill: 1, - color: Colors.white, - ), + icon: recording.value + ? const Icon(Symbols.stop, fill: 1, color: Colors.white) + : const Icon(Symbols.play_arrow, fill: 1, color: Colors.white), ), ], ), diff --git a/lib/widgets/post/compose_settings_sheet.dart b/lib/posts/posts_widgets/post/compose_settings_sheet.dart similarity index 97% rename from lib/widgets/post/compose_settings_sheet.dart rename to lib/posts/posts_widgets/post/compose_settings_sheet.dart index 573a76a7..8c6aa3a5 100644 --- a/lib/widgets/post/compose_settings_sheet.dart +++ b/lib/posts/posts_widgets/post/compose_settings_sheet.dart @@ -5,15 +5,15 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post_category.dart'; -import 'package:island/models/post_tag.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/post/post_categories.dart'; -import 'package:island/screens/realm/realms.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/post/compose_shared.dart'; +import 'package:island/posts/post/post_categories.dart'; +import 'package:island/posts/posts_models/post_category.dart'; +import 'package:island/posts/posts_models/post_tag.dart'; +import 'package:island/posts/posts_widgets/post/compose_shared.dart'; +import 'package:island/realms/realm/realms.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/core/network.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/post/compose_shared.dart b/lib/posts/posts_widgets/post/compose_shared.dart similarity index 96% rename from lib/widgets/post/compose_shared.dart rename to lib/posts/posts_widgets/post/compose_shared.dart index 04e6b35b..75324227 100644 --- a/lib/widgets/post/compose_shared.dart +++ b/lib/posts/posts_widgets/post/compose_shared.dart @@ -1,8 +1,12 @@ import 'dart:async'; import 'package:collection/collection.dart'; -import 'package:island/services/event_bus.dart'; -import 'package:island/widgets/post/compose_settings_sheet.dart'; +import 'package:island/core/services/event_bus.dart'; +import 'package:island/posts/posts_widgets/post/compose_fund.dart'; +import 'package:island/posts/posts_widgets/post/compose_link_attachments.dart'; +import 'package:island/posts/posts_widgets/post/compose_poll.dart'; +import 'package:island/posts/posts_widgets/post/compose_recorder.dart'; +import 'package:island/posts/posts_widgets/post/compose_settings_sheet.dart'; import 'package:mime/mime.dart'; import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -11,23 +15,19 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/post.dart'; -import 'package:island/models/post_category.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/services/file_uploader.dart'; -import 'package:island/services/compose_storage_db.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/post/compose_link_attachments.dart'; -import 'package:island/widgets/post/compose_poll.dart'; -import 'package:island/widgets/post/compose_fund.dart'; -import 'package:island/widgets/post/compose_recorder.dart'; -import 'package:island/pods/drive/file_pool.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/posts_models/post_category.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/core/network.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/posts/compose_storage_db.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/drive/drive/file_pool.dart'; import 'package:pasteboard/pasteboard.dart'; import 'package:island/talker.dart'; -import 'package:island/services/analytics_service.dart'; +import 'package:island/core/services/analytics_service.dart'; class ComposeState { final TextEditingController titleController; diff --git a/lib/posts/posts_widgets/post/compose_sheet.dart b/lib/posts/posts_widgets/post/compose_sheet.dart new file mode 100644 index 00000000..71f992ab --- /dev/null +++ b/lib/posts/posts_widgets/post/compose_sheet.dart @@ -0,0 +1,328 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts/post_detail.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/compose.dart'; +import 'package:island/posts/compose_storage_db.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/posts/posts_widgets/post/compose_card.dart'; +import 'package:island/posts/posts_widgets/post/compose_shared.dart'; +import 'package:island/posts/posts_widgets/post/compose_state_utils.dart'; +import 'package:material_symbols_icons/symbols.dart'; + +/// A dialog that wraps PostComposeCard for easy use in dialogs. +/// This provides a convenient way to show the compose interface in a modal dialog. +class PostComposeSheet extends HookConsumerWidget { + final SnPost? originalPost; + final PostComposeInitialState? initialState; + final bool isBottomSheet; + + const PostComposeSheet({ + super.key, + this.originalPost, + this.initialState, + this.isBottomSheet = false, + }); + + static Future show( + BuildContext context, { + SnPost? originalPost, + PostComposeInitialState? initialState, + }) { + // Check if editing an article + if (originalPost != null && originalPost.type == 1) { + context.pushNamed('articleEdit', pathParameters: {'id': originalPost.id}); + return Future.value(true); + } + + return showModalBottomSheet( + context: context, + isScrollControlled: true, + useRootNavigator: true, + builder: (context) => PostComposeSheet( + originalPost: originalPost, + initialState: initialState, + isBottomSheet: true, + ), + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final drafts = ref.watch(composeStorageProvider); + final restoredInitialState = useState(null); + final prompted = useState(false); + + // Fetch full post data if we're editing a post + final fullPostData = originalPost != null + ? ref.watch(postProvider(originalPost!.id)) + : const AsyncValue.data(null); + + // Use the full post data if available, otherwise fall back to originalPost + final effectiveOriginalPost = fullPostData.when( + data: (fullPost) => fullPost ?? originalPost, + loading: () => originalPost, + error: (_, _) => originalPost, + ); + + final repliedPost = + initialState?.replyingTo ?? effectiveOriginalPost?.repliedPost; + final forwardedPost = + initialState?.forwardingTo ?? effectiveOriginalPost?.forwardedPost; + + // Create compose state + final ComposeState state = useMemoized( + () => ComposeLogic.createState( + originalPost: effectiveOriginalPost, + forwardedPost: forwardedPost, + repliedPost: repliedPost, + postType: 0, + ), + [effectiveOriginalPost, forwardedPost, repliedPost], + ); + + // Add a listener to the entire state to trigger rebuilds + final stateNotifier = useMemoized( + () => Listenable.merge([ + state.titleController, + state.descriptionController, + state.contentController, + state.visibility, + state.attachments, + state.attachmentProgress, + state.currentPublisher, + state.submitting, + ]), + [state], + ); + useListenable(stateNotifier); + + // Use shared state management utilities + ComposeStateUtils.usePublisherInitialization(ref, state); + ComposeStateUtils.useInitialStateLoader(state, initialState); + + useEffect(() { + if (!prompted.value && + originalPost == null && + initialState?.replyingTo == null && + initialState?.forwardingTo == null && + drafts.isNotEmpty) { + prompted.value = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + _showRestoreDialog(ref, restoredInitialState); + }); + } + return null; + }, [drafts, prompted.value]); + + // Dispose state when widget is disposed + useEffect( + () => + () => ComposeLogic.dispose(state), + [], + ); + + // Helper methods for actions + void showSettingsSheet() { + ComposeLogic.showSettingsSheet(context, state); + } + + Future performSubmit() async { + await ComposeLogic.performSubmit( + ref, + state, + context, + originalPost: effectiveOriginalPost, + repliedPost: repliedPost, + forwardedPost: forwardedPost, + onSuccess: () { + Navigator.of(context).pop(true); + }, + ); + } + + final actions = [ + IconButton( + icon: const Icon(Symbols.settings), + onPressed: showSettingsSheet, + tooltip: 'postSettings'.tr(), + ), + IconButton( + onPressed: + (state.submitting.value || state.currentPublisher.value == null) + ? null + : performSubmit, + icon: state.submitting.value + ? SizedBox( + width: 24, + height: 24, + child: const CircularProgressIndicator(strokeWidth: 2), + ) + : Icon( + effectiveOriginalPost != null ? Symbols.edit : Symbols.upload, + ), + tooltip: effectiveOriginalPost != null + ? 'postUpdate'.tr() + : 'postPublish'.tr(), + ), + ]; + + // Tablet will show a virtual keyboard, so we adjust the height factor accordingly + final isTablet = + isWideScreen(context) && + !kIsWeb && + (Platform.isAndroid || Platform.isIOS); + + return SheetScaffold( + heightFactor: isTablet ? 0.95 : 0.8, + titleText: 'postCompose'.tr(), + actions: actions, + child: PostComposeCard( + originalPost: effectiveOriginalPost, + initialState: restoredInitialState.value ?? initialState, + onCancel: () => Navigator.of(context).pop(), + onSubmit: () { + Navigator.of(context).pop(true); + }, + isContained: true, + showHeader: false, + providedState: state, + ), + ); + } + + Future _showRestoreDialog( + WidgetRef ref, + ValueNotifier restoredInitialState, + ) async { + final drafts = ref.read(composeStorageProvider); + if (drafts.isNotEmpty) { + final latestDraft = drafts.values.last; + + final restore = await showDialog( + context: ref.context, + useRootNavigator: true, + builder: (context) => AlertDialog( + title: Text('restoreDraftTitle'.tr()), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('restoreDraftMessage'.tr()), + const SizedBox(height: 16), + _buildCompactDraftPreview(context, latestDraft), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text('no'.tr()), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text('yes'.tr()), + ), + ], + ), + ); + if (restore == true) { + // Delete the old draft + await ref + .read(composeStorageProvider.notifier) + .deleteDraft(latestDraft.id); + restoredInitialState.value = PostComposeInitialState( + title: latestDraft.title, + description: latestDraft.description, + content: latestDraft.content, + visibility: latestDraft.visibility, + attachments: latestDraft.attachments + .map((e) => UniversalFile.fromAttachment(e)) + .toList(), + ); + } + } + } + + Widget _buildCompactDraftPreview(BuildContext context, SnPost draft) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Theme.of(context).colorScheme.outline.withOpacity(0.3), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.description, + size: 16, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 8), + Text( + 'draft'.tr(), + style: Theme.of(context).textTheme.labelMedium?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + const SizedBox(height: 8), + if (draft.title?.isNotEmpty ?? false) + Text( + draft.title!, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: Theme.of(context).colorScheme.onSurface, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (draft.content?.isNotEmpty ?? false) + Text( + draft.content!, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (draft.attachments.isNotEmpty) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.attach_file, + size: 12, + color: Theme.of(context).colorScheme.secondary, + ), + const SizedBox(width: 4), + Text( + '${draft.attachments.length} attachment${draft.attachments.length > 1 ? 's' : ''}', + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontSize: 11, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/widgets/post/compose_state_utils.dart b/lib/posts/posts_widgets/post/compose_state_utils.dart similarity index 90% rename from lib/widgets/post/compose_state_utils.dart rename to lib/posts/posts_widgets/post/compose_state_utils.dart index 9211daf6..9d5d08f8 100644 --- a/lib/widgets/post/compose_state_utils.dart +++ b/lib/posts/posts_widgets/post/compose_state_utils.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/post.dart'; -import 'package:island/screens/creators/publishers_form.dart'; -import 'package:island/screens/posts/compose.dart'; -import 'package:island/services/compose_storage_db.dart'; -import 'package:island/widgets/post/compose_shared.dart'; +import 'package:island/creators/creators/publishers_form.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/compose.dart'; +import 'package:island/posts/compose_storage_db.dart'; +import 'package:island/posts/posts_widgets/post/compose_shared.dart'; /// Utility class for common compose state management logic. class ComposeStateUtils { @@ -65,8 +65,8 @@ class ComposeStateUtils { final mostRecentDraft = drafts.values.reduce( (a, b) => (a.updatedAt ?? DateTime(0)).isAfter(b.updatedAt ?? DateTime(0)) - ? a - : b, + ? a + : b, ); // Only load if the draft has meaningful content @@ -119,11 +119,10 @@ class ComposeStateUtils { content: state.contentController.text, visibility: state.visibility.value, type: state.postType, - attachments: - state.attachments.value - .where((e) => e.isOnCloud) - .map((e) => e.data as SnCloudFile) - .toList(), + attachments: state.attachments.value + .where((e) => e.isOnCloud) + .map((e) => e.data as SnCloudFile) + .toList(), publisher: state.currentPublisher.value!, updatedAt: DateTime.now(), ); diff --git a/lib/widgets/post/compose_toolbar.dart b/lib/posts/posts_widgets/post/compose_toolbar.dart similarity index 95% rename from lib/widgets/post/compose_toolbar.dart rename to lib/posts/posts_widgets/post/compose_toolbar.dart index 901d4324..e33c9949 100644 --- a/lib/widgets/post/compose_toolbar.dart +++ b/lib/posts/posts_widgets/post/compose_toolbar.dart @@ -1,12 +1,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/services/compose_storage_db.dart'; -import 'package:island/widgets/post/compose_embed_sheet.dart'; -import 'package:island/widgets/post/compose_shared.dart'; -import 'package:island/widgets/post/draft_manager.dart'; -import 'package:island/widgets/shared/upload_menu.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/compose_storage_db.dart'; +import 'package:island/core/widgets/shared/upload_menu.dart'; +import 'package:island/posts/posts_widgets/post/compose_embed_sheet.dart'; +import 'package:island/posts/posts_widgets/post/compose_shared.dart'; +import 'package:island/posts/posts_widgets/post/draft_manager.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/post/draft_manager.dart b/lib/posts/posts_widgets/post/draft_manager.dart similarity index 94% rename from lib/widgets/post/draft_manager.dart rename to lib/posts/posts_widgets/post/draft_manager.dart index 4f613d59..a58cf396 100644 --- a/lib/widgets/post/draft_manager.dart +++ b/lib/posts/posts_widgets/post/draft_manager.dart @@ -3,9 +3,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/services/compose_storage_db.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/posts/compose_storage_db.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; class DraftManagerSheet extends HookConsumerWidget { @@ -31,11 +31,10 @@ class DraftManagerSheet extends HookConsumerWidget { final query = searchQuery.value.toLowerCase(); return drafts.values.where((draft) { - return (draft.title?.toLowerCase().contains(query) ?? false) || - (draft.description?.toLowerCase().contains(query) ?? false) || - (draft.content?.toLowerCase().contains(query) ?? false); - }).toList() - ..sort((a, b) => b.updatedAt!.compareTo(a.updatedAt!)); + return (draft.title?.toLowerCase().contains(query) ?? false) || + (draft.description?.toLowerCase().contains(query) ?? false) || + (draft.content?.toLowerCase().contains(query) ?? false); + }).toList()..sort((a, b) => b.updatedAt!.compareTo(a.updatedAt!)); }, [drafts, searchQuery.value]); return SheetScaffold( @@ -159,8 +158,9 @@ class _DraftItem extends StatelessWidget { final title = draft.title ?? 'untitled'.tr(); final content = draft.content ?? (draft.description ?? 'noContent'.tr()); - final preview = - content.length > 100 ? '${content.substring(0, 100)}...' : content; + final preview = content.length > 100 + ? '${content.substring(0, 100)}...' + : content; final timeAgo = _formatTimeAgo(draft.updatedAt!); final visibility = _parseVisibility(draft.visibility).tr(); diff --git a/lib/widgets/post/embed_view_renderer.dart b/lib/posts/posts_widgets/post/embed_view_renderer.dart similarity index 99% rename from lib/widgets/post/embed_view_renderer.dart rename to lib/posts/posts_widgets/post/embed_view_renderer.dart index e87dee5a..03656f77 100644 --- a/lib/widgets/post/embed_view_renderer.dart +++ b/lib/posts/posts_widgets/post/embed_view_renderer.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; +import 'package:island/posts/posts_models/post.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/lib/widgets/post/filters/post_filter.dart b/lib/posts/posts_widgets/post/filters/post_filter.dart similarity index 99% rename from lib/widgets/post/filters/post_filter.dart rename to lib/posts/posts_widgets/post/filters/post_filter.dart index b05797fb..1ff7b0b0 100644 --- a/lib/widgets/post/filters/post_filter.dart +++ b/lib/posts/posts_widgets/post/filters/post_filter.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/post/post_list.dart'; +import 'package:island/posts/post/post_list.dart'; import 'package:material_symbols_icons/symbols.dart'; class PostFilterWidget extends HookConsumerWidget { diff --git a/lib/widgets/post/filters/post_subscription_filter.dart b/lib/posts/posts_widgets/post/filters/post_subscription_filter.dart similarity index 97% rename from lib/widgets/post/filters/post_subscription_filter.dart rename to lib/posts/posts_widgets/post/filters/post_subscription_filter.dart index 9730503a..4821b393 100644 --- a/lib/widgets/post/filters/post_subscription_filter.dart +++ b/lib/posts/posts_widgets/post/filters/post_subscription_filter.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/models/post_category.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/posts_models/post_category.dart'; +import 'package:island/core/network.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/post/filters/post_subscription_filter.g.dart b/lib/posts/posts_widgets/post/filters/post_subscription_filter.g.dart similarity index 100% rename from lib/widgets/post/filters/post_subscription_filter.g.dart rename to lib/posts/posts_widgets/post/filters/post_subscription_filter.g.dart diff --git a/lib/widgets/post/post_award_history_sheet.dart b/lib/posts/posts_widgets/post/post_award_history_sheet.dart similarity index 93% rename from lib/widgets/post/post_award_history_sheet.dart rename to lib/posts/posts_widgets/post/post_award_history_sheet.dart index cb41c516..6e0ea542 100644 --- a/lib/widgets/post/post_award_history_sheet.dart +++ b/lib/posts/posts_widgets/post/post_award_history_sheet.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; final postAwardListNotifierProvider = AsyncNotifierProvider.autoDispose.family( PostAwardListNotifier.new, diff --git a/lib/widgets/post/post_award_sheet.dart b/lib/posts/posts_widgets/post/post_award_sheet.dart similarity index 96% rename from lib/widgets/post/post_award_sheet.dart rename to lib/posts/posts_widgets/post/post_award_sheet.dart index 96cd0db6..c908032b 100644 --- a/lib/widgets/post/post_award_sheet.dart +++ b/lib/posts/posts_widgets/post/post_award_sheet.dart @@ -3,13 +3,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/models/wallet.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/payment/payment_overlay.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/wallet/wallet_models/wallet.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/core/widgets/payment/payment_overlay.dart'; import 'package:material_symbols_icons/symbols.dart'; class PostAwardSheet extends HookConsumerWidget { diff --git a/lib/widgets/post/post_featured.dart b/lib/posts/posts_widgets/post/post_featured.dart similarity index 97% rename from lib/widgets/post/post_featured.dart rename to lib/posts/posts_widgets/post/post_featured.dart index f2b658d3..9a2adfbd 100644 --- a/lib/widgets/post/post_featured.dart +++ b/lib/posts/posts_widgets/post/post_featured.dart @@ -2,14 +2,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/core/network.dart'; +import 'package:island/posts/posts_widgets/post/post_item.dart'; import 'package:island/talker.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:island/widgets/post/post_item.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:island/pods/config.dart'; // Import config.dart for shared preferences keys and provider +import 'package:island/core/config.dart'; // Import config.dart for shared preferences keys and provider part 'post_featured.g.dart'; diff --git a/lib/widgets/post/post_featured.g.dart b/lib/posts/posts_widgets/post/post_featured.g.dart similarity index 100% rename from lib/widgets/post/post_featured.g.dart rename to lib/posts/posts_widgets/post/post_featured.g.dart diff --git a/lib/widgets/post/post_item.dart b/lib/posts/posts_widgets/post/post_item.dart similarity index 95% rename from lib/widgets/post/post_item.dart rename to lib/posts/posts_widgets/post/post_item.dart index 13fd75f4..8080e0f3 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/posts/posts_widgets/post/post_item.dart @@ -7,28 +7,28 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/translate.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/screens/posts/compose.dart'; -import 'package:island/utils/share_utils.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/markdown.dart'; -import 'package:island/widgets/content/image.dart'; -import 'package:island/widgets/post/post_award_sheet.dart'; -import 'package:island/widgets/post/post_pin_sheet.dart'; -import 'package:island/widgets/post/post_shared.dart'; -import 'package:island/widgets/post/embed_view_renderer.dart'; -import 'package:island/widgets/post/post_reaction_sheet.dart'; -import 'package:island/widgets/safety/abuse_report_helper.dart'; -import 'package:island/widgets/share/share_sheet.dart'; -import 'package:island/widgets/post/compose_sheet.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/translate.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/posts/compose.dart'; +import 'package:island/core/utils/share_utils.dart'; +import 'package:island/posts/posts_widgets/post/embed_view_renderer.dart'; +import 'package:island/posts/posts_widgets/post/post_award_sheet.dart'; +import 'package:island/posts/posts_widgets/post/post_pin_sheet.dart'; +import 'package:island/posts/posts_widgets/post/post_reaction_sheet.dart'; +import 'package:island/posts/posts_widgets/post/post_shared.dart'; +import 'package:island/reports/reports_widgets/safety/abuse_report_helper.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/markdown.dart'; +import 'package:island/core/widgets/content/image.dart'; +import 'package:island/core/widgets/share/share_sheet.dart'; +import 'package:island/posts/posts_widgets/compose_sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:super_context_menu/super_context_menu.dart'; -import 'package:island/services/analytics_service.dart'; +import 'package:island/core/services/analytics_service.dart'; const kAvailableStickers = { 'angry', diff --git a/lib/widgets/post/post_item_creator.dart b/lib/posts/posts_widgets/post/post_item_creator.dart similarity index 96% rename from lib/widgets/post/post_item_creator.dart rename to lib/posts/posts_widgets/post/post_item_creator.dart index e7056ac0..de9f1869 100644 --- a/lib/widgets/post/post_item_creator.dart +++ b/lib/posts/posts_widgets/post/post_item_creator.dart @@ -4,13 +4,13 @@ import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/post/post_item.dart'; -import 'package:island/widgets/post/post_shared.dart'; -import 'package:island/widgets/post/compose_sheet.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/posts/posts_widgets/post/post_item.dart'; +import 'package:island/posts/posts_widgets/post/post_shared.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/posts/posts_widgets/compose_sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:super_context_menu/super_context_menu.dart'; diff --git a/lib/widgets/post/post_item_screenshot.dart b/lib/posts/posts_widgets/post/post_item_screenshot.dart similarity index 96% rename from lib/widgets/post/post_item_screenshot.dart rename to lib/posts/posts_widgets/post/post_item_screenshot.dart index da721799..daf10668 100644 --- a/lib/widgets/post/post_item_screenshot.dart +++ b/lib/posts/posts_widgets/post/post_item_screenshot.dart @@ -3,12 +3,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/widgets/post/post_shared.dart'; -import 'package:island/widgets/content/image.dart'; -import 'package:island/widgets/content/markdown.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/widgets/content/image.dart'; +import 'package:island/core/widgets/content/markdown.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/posts/posts_widgets/post/post_shared.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/post/post_item_skeleton.dart b/lib/posts/posts_widgets/post/post_item_skeleton.dart similarity index 100% rename from lib/widgets/post/post_item_skeleton.dart rename to lib/posts/posts_widgets/post/post_item_skeleton.dart diff --git a/lib/widgets/post/post_list.dart b/lib/posts/posts_widgets/post/post_list.dart similarity index 87% rename from lib/widgets/post/post_list.dart rename to lib/posts/posts_widgets/post/post_list.dart index 002d81e6..99aa912b 100644 --- a/lib/widgets/post/post_list.dart +++ b/lib/posts/posts_widgets/post/post_list.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/post/post_list.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; -import 'package:island/widgets/post/post_item.dart'; -import 'package:island/widgets/post/post_item_creator.dart'; -import 'package:island/widgets/post/post_item_skeleton.dart'; +import 'package:island/posts/post/post_list.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/posts_widgets/post/post_item.dart'; +import 'package:island/posts/posts_widgets/post/post_item_creator.dart'; +import 'package:island/posts/posts_widgets/post/post_item_skeleton.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; /// Defines which post item widget to use in the list enum PostItemType { diff --git a/lib/widgets/post/post_pin_sheet.dart b/lib/posts/posts_widgets/post/post_pin_sheet.dart similarity index 81% rename from lib/widgets/post/post_pin_sheet.dart rename to lib/posts/posts_widgets/post/post_pin_sheet.dart index 5ca390cf..cadff96d 100644 --- a/lib/widgets/post/post_pin_sheet.dart +++ b/lib/posts/posts_widgets/post/post_pin_sheet.dart @@ -2,10 +2,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -61,24 +61,21 @@ class PostPinSheet extends HookConsumerWidget { leading: Radio( value: 1, groupValue: mode.value, - onChanged: - post.realmId != null && post.realmId!.isNotEmpty - ? (value) { - mode.value = value!; - } - : null, + onChanged: post.realmId != null && post.realmId!.isNotEmpty + ? (value) { + mode.value = value!; + } + : null, ), title: Text('realmPage'.tr()), - subtitle: - post.realmId != null && post.realmId!.isNotEmpty - ? Text('pinPostRealmHint'.tr()) - : Text('pinPostRealmDisabledHint'.tr()), - onTap: - post.realmId != null && post.realmId!.isNotEmpty - ? () { - mode.value = 1; - } - : null, + subtitle: post.realmId != null && post.realmId!.isNotEmpty + ? Text('pinPostRealmHint'.tr()) + : Text('pinPostRealmDisabledHint'.tr()), + onTap: post.realmId != null && post.realmId!.isNotEmpty + ? () { + mode.value = 1; + } + : null, enabled: post.realmId != null && post.realmId!.isNotEmpty, ), diff --git a/lib/widgets/post/post_quick_reply.dart b/lib/posts/posts_widgets/post/post_quick_reply.dart similarity index 90% rename from lib/widgets/post/post_quick_reply.dart rename to lib/posts/posts_widgets/post/post_quick_reply.dart index b2127a5b..16c206b5 100644 --- a/lib/widgets/post/post_quick_reply.dart +++ b/lib/posts/posts_widgets/post/post_quick_reply.dart @@ -3,16 +3,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/creators/publishers_form.dart'; -import 'package:island/screens/posts/compose.dart'; -import 'package:island/services/event_bus.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/post/compose_sheet.dart'; -import 'package:island/widgets/post/publishers_modal.dart'; +import 'package:island/creators/creators/publishers_form.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/core/network.dart'; +import 'package:island/posts/compose.dart'; +import 'package:island/core/services/event_bus.dart'; +import 'package:island/posts/posts_widgets/post/publishers_modal.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/posts/posts_widgets/compose_sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/post/post_reaction_sheet.dart b/lib/posts/posts_widgets/post/post_reaction_sheet.dart similarity index 97% rename from lib/widgets/post/post_reaction_sheet.dart rename to lib/posts/posts_widgets/post/post_reaction_sheet.dart index c0eac698..08680a11 100644 --- a/lib/widgets/post/post_reaction_sheet.dart +++ b/lib/posts/posts_widgets/post/post_reaction_sheet.dart @@ -6,19 +6,19 @@ import 'package:flutter_popup_card/flutter_popup_card.dart'; import 'package:gap/gap.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/account/account_pfc.dart'; -import 'package:island/widgets/activitypub/actor_profile.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/accounts/accounts_widgets/account/account_pfc.dart'; +import 'package:island/accounts/accounts_widgets/activitypub/actor_profile.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; +import 'package:island/stickers/stickers_widgets/stickers/sticker_picker.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:island/widgets/stickers/sticker_picker.dart'; -import 'package:island/pods/config.dart'; +import 'package:island/core/config.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'post_reaction_sheet.freezed.dart'; @@ -514,7 +514,7 @@ class ReactionDetailsPopup extends HookConsumerWidget { padding: EdgeInsets.zero, itemBuilder: (context, index, reaction) { return ListTile( - leading: AccountPfcGestureDetector( + leading: AccountPfcRegion( uname: reaction.account?.name, child: reaction.actor != null ? ActorPictureWidget( diff --git a/lib/widgets/post/post_reaction_sheet.freezed.dart b/lib/posts/posts_widgets/post/post_reaction_sheet.freezed.dart similarity index 100% rename from lib/widgets/post/post_reaction_sheet.freezed.dart rename to lib/posts/posts_widgets/post/post_reaction_sheet.freezed.dart diff --git a/lib/widgets/post/post_replies.dart b/lib/posts/posts_widgets/post/post_replies.dart similarity index 87% rename from lib/widgets/post/post_replies.dart rename to lib/posts/posts_widgets/post/post_replies.dart index 4473427a..ff7757c9 100644 --- a/lib/widgets/post/post_replies.dart +++ b/lib/posts/posts_widgets/post/post_replies.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; -import 'package:island/widgets/post/post_item.dart'; -import 'package:island/widgets/post/post_item_skeleton.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/core/network.dart'; +import 'package:island/posts/posts_widgets/post/post_item.dart'; +import 'package:island/posts/posts_widgets/post/post_item_skeleton.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; final postRepliesProvider = AsyncNotifierProvider.autoDispose.family( PostRepliesNotifier.new, diff --git a/lib/widgets/post/post_replies_sheet.dart b/lib/posts/posts_widgets/post/post_replies_sheet.dart similarity index 56% rename from lib/widgets/post/post_replies_sheet.dart rename to lib/posts/posts_widgets/post/post_replies_sheet.dart index 86a2ab87..2aa6d2ce 100644 --- a/lib/widgets/post/post_replies_sheet.dart +++ b/lib/posts/posts_widgets/post/post_replies_sheet.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/post/post_replies.dart'; -import 'package:island/widgets/post/post_quick_reply.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:island/posts/posts_widgets/post/post_quick_reply.dart'; +import 'package:island/posts/posts_widgets/post/post_replies.dart'; import 'package:styled_widget/styled_widget.dart'; class PostRepliesSheet extends HookConsumerWidget { @@ -38,19 +38,20 @@ class PostRepliesSheet extends HookConsumerWidget { bottom: 0, left: 0, right: 0, - child: PostQuickReply( - parent: post, - onPosted: () { - ref.invalidate(postRepliesProvider(post.id)); - }, - onLaunch: () { - Navigator.of(context).pop(); - }, - ).padding( - bottom: MediaQuery.of(context).padding.bottom + 16, - top: 8, - horizontal: 16, - ), + child: + PostQuickReply( + parent: post, + onPosted: () { + ref.invalidate(postRepliesProvider(post.id)); + }, + onLaunch: () { + Navigator.of(context).pop(); + }, + ).padding( + bottom: MediaQuery.of(context).padding.bottom + 16, + top: 8, + horizontal: 16, + ), ), ], ), diff --git a/lib/widgets/post/post_shared.dart b/lib/posts/posts_widgets/post/post_shared.dart similarity index 97% rename from lib/widgets/post/post_shared.dart rename to lib/posts/posts_widgets/post/post_shared.dart index 04612d76..da4b803d 100644 --- a/lib/widgets/post/post_shared.dart +++ b/lib/posts/posts_widgets/post/post_shared.dart @@ -7,19 +7,19 @@ import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:html2md/html2md.dart' as html2md; -import 'package:island/models/account.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/post.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/account/account_name.dart'; -import 'package:island/widgets/activitypub/actor_profile.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/cloud_file_collection.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/embed/embed_list.dart'; -import 'package:island/widgets/content/markdown.dart'; -import 'package:island/widgets/post/post_replies_sheet.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/accounts/accounts_widgets/account/account_name.dart'; +import 'package:island/accounts/accounts_widgets/activitypub/actor_profile.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/posts/posts_widgets/post/post_replies_sheet.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/cloud_file_collection.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/embed/embed_list.dart'; +import 'package:island/core/widgets/content/markdown.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/post/post_shared.g.dart b/lib/posts/posts_widgets/post/post_shared.g.dart similarity index 100% rename from lib/widgets/post/post_shared.g.dart rename to lib/posts/posts_widgets/post/post_shared.g.dart diff --git a/lib/widgets/post/post_shuffle.dart b/lib/posts/posts_widgets/post/post_shuffle.dart similarity index 97% rename from lib/widgets/post/post_shuffle.dart rename to lib/posts/posts_widgets/post/post_shuffle.dart index fd04a429..113d785d 100644 --- a/lib/widgets/post/post_shuffle.dart +++ b/lib/posts/posts_widgets/post/post_shuffle.dart @@ -2,9 +2,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/post/post_list.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/post/post_item.dart'; +import 'package:island/posts/post/post_list.dart'; +import 'package:island/posts/posts_widgets/post/post_item.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; import 'package:styled_widget/styled_widget.dart'; const kShufflePostListId = 'shuffle'; diff --git a/lib/widgets/post/publishers_modal.dart b/lib/posts/posts_widgets/post/publishers_modal.dart similarity index 96% rename from lib/widgets/post/publishers_modal.dart rename to lib/posts/posts_widgets/post/publishers_modal.dart index 0ff03f5c..264648b5 100644 --- a/lib/widgets/post/publishers_modal.dart +++ b/lib/posts/posts_widgets/post/publishers_modal.dart @@ -4,8 +4,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/screens/creators/publishers_form.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/creators/creators/publishers_form.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:styled_widget/styled_widget.dart'; class PublisherModal extends HookConsumerWidget { diff --git a/lib/widgets/publisher/publisher_card.dart b/lib/posts/posts_widgets/publisher/publisher_card.dart similarity index 96% rename from lib/widgets/publisher/publisher_card.dart rename to lib/posts/posts_widgets/publisher/publisher_card.dart index 27593c0d..8dc28e05 100644 --- a/lib/widgets/publisher/publisher_card.dart +++ b/lib/posts/posts_widgets/publisher/publisher_card.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/publisher.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; class PublisherDiscoveryCard extends ConsumerWidget { final SnPublisher publisher; diff --git a/lib/posts/publisher_profile.dart b/lib/posts/publisher_profile.dart new file mode 100644 index 00000000..5377bf19 --- /dev/null +++ b/lib/posts/publisher_profile.dart @@ -0,0 +1,756 @@ +import 'package:dio/dio.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:gap/gap.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/accounts/accounts_widgets/account/account_name.dart'; +import 'package:island/accounts/accounts_widgets/account/badge.dart'; +import 'package:island/accounts/accounts_widgets/account/status.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/posts/post/post_list.dart'; +import 'package:island/posts/posts_models/post.dart'; +import 'package:island/posts/posts_models/publisher.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/posts/posts_models/heatmap.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/posts/posts_widgets/post/filters/post_filter.dart'; +import 'package:island/posts/posts_widgets/post/post_item.dart'; +import 'package:island/posts/posts_widgets/post/post_list.dart'; +import 'package:island/core/services/color.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/core/widgets/content/markdown.dart'; +import 'package:island/posts/activity_heatmap.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:island/core/services/color_extraction.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:styled_widget/styled_widget.dart'; + +part 'publisher_profile.g.dart'; + +class _PinnedPostsPageView extends HookConsumerWidget { + final String pubName; + + const _PinnedPostsPageView({required this.pubName}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final provider = postListProvider( + PostListQueryConfig( + id: 'publisher-$pubName-pinned', + initialFilter: PostListQuery(pubName: pubName, pinned: true), + ), + ); + final pinnedPosts = ref.watch(provider); + final pageController = usePageController(); + final currentPage = useState(0); + + useEffect(() { + void listener() { + currentPage.value = pageController.page?.round() ?? 0; + } + + pageController.addListener(listener); + return () => pageController.removeListener(listener); + }, [pageController]); + + return pinnedPosts.when( + data: (data) { + if (data.items.isEmpty) { + return const SizedBox.shrink(); + } + + return Card( + margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Theme( + data: Theme.of(context).copyWith(dividerColor: Colors.transparent), + child: ExpansionTile( + initiallyExpanded: true, + leading: const Icon(Symbols.push_pin), + title: Text('pinnedPosts'.tr()), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + collapsedShape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + children: [ + SizedBox( + height: 400, + child: Stack( + children: [ + PageView.builder( + controller: pageController, + itemCount: data.items.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.all(8), + child: SingleChildScrollView( + child: Card( + child: PostActionableItem( + item: data.items[index], + borderRadius: 8, + ), + ), + ), + ); + }, + ), + Positioned( + bottom: 16, + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + data.items.length, + (index) => AnimatedContainer( + duration: const Duration(milliseconds: 200), + margin: EdgeInsets.symmetric(horizontal: 4), + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: index == currentPage.value + ? Theme.of(context).colorScheme.primary + : Theme.of( + context, + ).colorScheme.primary.withOpacity(0.5), + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + }, + loading: () => const SizedBox.shrink(), + error: (_, _) => const SizedBox.shrink(), + ); + } +} + +class _PublisherBasisWidget extends StatelessWidget { + final SnPublisher data; + final AsyncValue subStatus; + final ValueNotifier subscribing; + final VoidCallback subscribe; + final VoidCallback unsubscribe; + + const _PublisherBasisWidget({ + required this.data, + required this.subStatus, + required this.subscribing, + required this.subscribe, + required this.unsubscribe, + }); + + @override + Widget build(BuildContext context) { + return Card( + child: Builder( + builder: (context) { + final hasBackground = data.background != null; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (isWideScreen(context) && hasBackground) + Stack( + clipBehavior: Clip.none, + children: [ + ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8), + topRight: Radius.circular(8), + ), + child: AspectRatio( + aspectRatio: 16 / 7, + child: CloudImageWidget( + file: data.background, + fit: BoxFit.cover, + ), + ), + ), + Positioned( + bottom: -24, + left: 16, + child: GestureDetector( + child: Badge( + isLabelVisible: data.type == 0, + padding: EdgeInsets.all(3), + label: Icon( + Symbols.launch, + size: 12, + color: Theme.of(context).colorScheme.onPrimary, + ), + backgroundColor: Theme.of( + context, + ).colorScheme.primary, + offset: Offset(0, 48), + child: ProfilePictureWidget( + file: data.picture, + radius: 32, + borderRadius: data.type == 0 ? null : 12, + ), + ), + onTap: () { + if (data.account?.name != null) { + Navigator.pop(context, true); + context.pushNamed( + 'accountProfile', + pathParameters: {'name': data.account!.name}, + ); + } + }, + ), + ), + ], + ), + Builder( + builder: (context) { + final showBackground = isWideScreen(context) && hasBackground; + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: showBackground ? 0 : 20, + children: [ + if (!showBackground) + GestureDetector( + child: Badge( + isLabelVisible: data.type == 0, + padding: EdgeInsets.all(4), + label: Icon( + Symbols.launch, + size: 16, + color: Theme.of(context).colorScheme.onPrimary, + ), + backgroundColor: Theme.of( + context, + ).colorScheme.primary, + offset: Offset(0, 48), + child: ProfilePictureWidget( + file: data.picture, + radius: 32, + borderRadius: data.type == 0 ? null : 12, + ), + ), + onTap: () { + if (data.account?.name != null) { + Navigator.pop(context, true); + context.pushNamed( + 'accountProfile', + pathParameters: {'name': data.account!.name}, + ); + } + }, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + spacing: 6, + children: [ + if (data.account != null && data.type == 0) + AccountName( + account: data.account!, + textOverride: data.nick, + hideVerificationMark: true, + style: TextStyle(fontSize: 20), + ) + else + Text(data.nick).fontSize(20), + if (data.verification != null) + VerificationMark(mark: data.verification!), + if (isWideScreen(context)) + Expanded( + child: Text( + '@${data.name}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + ).fontSize(14).opacity(0.85), + ), + ], + ), + if (!isWideScreen(context)) + Text( + '@${data.name}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + ).fontSize(14).opacity(0.85).padding(bottom: 2.5), + if (data.type == 0 && data.account != null) + Row( + crossAxisAlignment: CrossAxisAlignment.center, + spacing: 6, + children: [ + Icon( + data.type == 0 + ? Symbols.person + : Symbols.workspaces, + fill: 1, + size: 17, + ), + Text( + 'publisherBelongsTo'.tr( + args: ['@${data.account!.name}'], + ), + ).fontSize(14), + ], + ).opacity(0.85), + const Gap(4), + if (data.type == 0 && data.account != null) + AccountStatusWidget( + uname: data.account!.name, + padding: EdgeInsets.zero, + ), + subStatus + .when( + data: (status) => FilledButton.icon( + onPressed: subscribing.value + ? null + : (status != null + ? unsubscribe + : subscribe), + icon: Icon( + status != null + ? Symbols.remove_circle + : Symbols.add_circle, + ), + label: Text( + status != null + ? 'unsubscribe' + : 'subscribe', + ).tr(), + style: ButtonStyle( + visualDensity: VisualDensity( + vertical: -2, + ), + ), + ), + error: (_, _) => const SizedBox(), + loading: () => const SizedBox( + height: 36, + child: Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ), + ), + ), + ) + .padding(vertical: 12), + ], + ), + ), + ], + ).padding( + left: 16, + right: 16, + top: 16 + (showBackground ? 16 : 0), + ); + }, + ), + ], + ); + }, + ), + ); + } +} + +class _PublisherBadgesWidget extends StatelessWidget { + final SnPublisher data; + final AsyncValue> badges; + + const _PublisherBadgesWidget({required this.data, required this.badges}); + + @override + Widget build(BuildContext context) { + return (badges.value?.isNotEmpty ?? false) + ? Card( + child: BadgeList( + badges: badges.value!, + ).padding(horizontal: 26, vertical: 20), + ).padding(horizontal: 4) + : const SizedBox.shrink(); + } +} + +class _PublisherVerificationWidget extends StatelessWidget { + final SnPublisher data; + + const _PublisherVerificationWidget({required this.data}); + + @override + Widget build(BuildContext context) { + return (data.verification != null) + ? Card( + margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: VerificationStatusCard(mark: data.verification!), + ) + : const SizedBox.shrink(); + } +} + +class _PublisherBioWidget extends StatelessWidget { + final SnPublisher data; + + const _PublisherBioWidget({required this.data}); + + @override + Widget build(BuildContext context) { + return Card( + margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('bio').tr().bold().fontSize(15).padding(bottom: 8), + if (data.bio.isEmpty) + Text('descriptionNone').tr().italic() + else + MarkdownTextContent( + content: data.bio, + linesMargin: EdgeInsets.zero, + ), + ], + ).padding(horizontal: 20, vertical: 16), + ); + } +} + +class _PublisherHeatmapWidget extends StatelessWidget { + final AsyncValue heatmap; + final bool forceDense; + + const _PublisherHeatmapWidget({ + required this.heatmap, + this.forceDense = false, + }); + + @override + Widget build(BuildContext context) { + return heatmap.when( + data: (data) => data != null + ? ActivityHeatmapWidget( + heatmap: data, + forceDense: forceDense, + ).padding(horizontal: 8) + : const SizedBox.shrink(), + loading: () => const SizedBox.shrink(), + error: (_, _) => const SizedBox.shrink(), + ); + } +} + +@riverpod +Future publisher(Ref ref, String uname) async { + final apiClient = ref.watch(apiClientProvider); + final resp = await apiClient.get("/sphere/publishers/$uname"); + return SnPublisher.fromJson(resp.data); +} + +@riverpod +Future> publisherBadges(Ref ref, String pubName) async { + final pub = await ref.watch(publisherProvider(pubName).future); + if (pub.type != 0 || pub.account == null) return []; + final apiClient = ref.watch(apiClientProvider); + final resp = await apiClient.get( + "/pass/accounts/${pub.account!.name}/badges", + ); + return List.from( + resp.data.map((x) => SnAccountBadge.fromJson(x)), + ); +} + +@riverpod +Future publisherSubscriptionStatus( + Ref ref, + String pubName, +) async { + final apiClient = ref.watch(apiClientProvider); + try { + final resp = await apiClient.get( + "/sphere/publishers/$pubName/subscription", + ); + return SnPublisherSubscription.fromJson(resp.data); + } catch (err) { + if (err is DioException) { + if (err.response?.statusCode == 404) return null; + rethrow; + } + } + return null; +} + +@riverpod +Future publisherAppbarForcegroundColor(Ref ref, String pubName) async { + try { + final publisher = await ref.watch(publisherProvider(pubName).future); + if (publisher.background == null) return null; + final colors = await ColorExtractionService.getColorsFromImage( + CloudImageWidget.provider( + file: publisher.background!, + serverUrl: ref.watch(serverUrlProvider), + ), + ); + if (colors.isEmpty) return null; + final dominantColor = colors.first; + return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; + } catch (_) { + return null; + } +} + +@riverpod +Future publisherHeatmap(Ref ref, String uname) async { + final apiClient = ref.watch(apiClientProvider); + final resp = await apiClient.get('/sphere/publishers/$uname/heatmap'); + return SnHeatmap.fromJson(resp.data); +} + +class PublisherProfileScreen extends HookConsumerWidget { + final String name; + const PublisherProfileScreen({super.key, required this.name}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final publisher = ref.watch(publisherProvider(name)); + final badges = ref.watch(publisherBadgesProvider(name)); + final subStatus = ref.watch(publisherSubscriptionStatusProvider(name)); + final heatmap = ref.watch(publisherHeatmapProvider(name)); + final appbarColor = ref.watch( + publisherAppbarForcegroundColorProvider(name), + ); + + final categoryTabController = useTabController(initialLength: 3); + + final queryState = useState(PostListQuery(pubName: name)); + + final subscribing = useState(false); + + useEffect(() { + final index = switch (queryState.value.type) { + 0 => 1, + 1 => 2, + _ => 0, + }; + categoryTabController.index = index; + return null; + }, []); + + Future subscribe() async { + final apiClient = ref.watch(apiClientProvider); + subscribing.value = true; + try { + await apiClient.post( + "/sphere/publishers/$name/subscribe", + data: {'tier': 0}, + ); + ref.invalidate(publisherSubscriptionStatusProvider(name)); + HapticFeedback.heavyImpact(); + } catch (err) { + showErrorAlert(err); + } finally { + subscribing.value = false; + } + } + + Future unsubscribe() async { + final apiClient = ref.watch(apiClientProvider); + subscribing.value = true; + try { + await apiClient.post("/sphere/publishers/$name/unsubscribe"); + ref.invalidate(publisherSubscriptionStatusProvider(name)); + HapticFeedback.heavyImpact(); + } catch (err) { + showErrorAlert(err); + } finally { + subscribing.value = false; + } + } + + final appbarShadow = Shadow( + color: appbarColor.value?.invert ?? Colors.transparent, + blurRadius: 5.0, + offset: Offset(1.0, 1.0), + ); + + return publisher.when( + data: (data) => AppScaffold( + isNoBackground: false, + appBar: isWideScreen(context) + ? AppBar( + foregroundColor: appbarColor.value, + leading: PageBackButton( + color: appbarColor.value, + shadows: [appbarShadow], + ), + title: Text( + data.nick, + style: TextStyle( + color: + appbarColor.value ?? + Theme.of(context).appBarTheme.foregroundColor, + shadows: [appbarShadow], + ), + ), + ) + : null, + body: isWideScreen(context) + ? Row( + children: [ + Flexible( + flex: 4, + child: CustomScrollView( + slivers: [ + SliverGap(16), + SliverToBoxAdapter( + child: _PinnedPostsPageView(pubName: name), + ), + SliverToBoxAdapter( + child: PostFilterWidget( + categoryTabController: categoryTabController, + initialQuery: queryState.value, + onQueryChanged: (newQuery) => + queryState.value = newQuery, + ), + ), + SliverPostList( + query: queryState.value, + queryKey: 'publisher-$name', + ), + SliverGap(MediaQuery.of(context).padding.bottom + 16), + ], + ).padding(left: 8), + ), + Flexible( + flex: 3, + child: Align( + alignment: Alignment.topLeft, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _PublisherBasisWidget( + data: data, + subStatus: subStatus, + subscribing: subscribing, + subscribe: subscribe, + unsubscribe: unsubscribe, + ).padding(horizontal: 4, top: 20), + _PublisherBadgesWidget(data: data, badges: badges), + _PublisherVerificationWidget(data: data), + _PublisherBioWidget(data: data), + _PublisherHeatmapWidget( + heatmap: heatmap, + forceDense: true, + ).padding(vertical: 4), + ], + ), + ), + ), + ), + ], + ) + : CustomScrollView( + slivers: [ + SliverAppBar( + foregroundColor: appbarColor.value, + expandedHeight: 180, + pinned: true, + leading: PageBackButton( + color: appbarColor.value, + shadows: [appbarShadow], + ), + flexibleSpace: Stack( + children: [ + Positioned.fill( + child: data.background != null + ? CloudImageWidget(file: data.background) + : Container( + color: Theme.of( + context, + ).appBarTheme.backgroundColor, + ), + ), + FlexibleSpaceBar( + title: Text( + data.nick, + style: TextStyle( + color: + appbarColor.value ?? + Theme.of(context).appBarTheme.foregroundColor, + shadows: [appbarShadow], + ), + ), + background: + Container(), // Empty container since background is handled by Stack + ), + ], + ), + ), + SliverToBoxAdapter( + child: _PublisherBasisWidget( + data: data, + subStatus: subStatus, + subscribing: subscribing, + subscribe: subscribe, + unsubscribe: unsubscribe, + ).padding(horizontal: 4, top: 8), + ), + SliverToBoxAdapter( + child: _PublisherBadgesWidget(data: data, badges: badges), + ), + SliverToBoxAdapter( + child: _PublisherVerificationWidget(data: data), + ), + SliverToBoxAdapter(child: _PublisherBioWidget(data: data)), + SliverToBoxAdapter( + child: _PublisherHeatmapWidget( + heatmap: heatmap, + ).padding(vertical: 4), + ), + SliverToBoxAdapter( + child: _PinnedPostsPageView(pubName: name), + ), + SliverToBoxAdapter( + child: PostFilterWidget( + categoryTabController: categoryTabController, + initialQuery: queryState.value, + onQueryChanged: (newQuery) => queryState.value = newQuery, + ), + ), + SliverPostList( + key: ValueKey(queryState.value), + query: queryState.value, + queryKey: 'publisher-$name', + ), + SliverGap(MediaQuery.of(context).padding.bottom + 16), + ], + ), + ), + error: (error, stackTrace) => AppScaffold( + isNoBackground: false, + appBar: AppBar(leading: const PageBackButton()), + body: Center(child: Text(error.toString())), + ), + loading: () => AppScaffold( + isNoBackground: false, + appBar: AppBar(leading: const PageBackButton()), + body: Center(child: CircularProgressIndicator()), + ), + ); + } +} diff --git a/lib/posts/publisher_profile.g.dart b/lib/posts/publisher_profile.g.dart new file mode 100644 index 00000000..21edc343 --- /dev/null +++ b/lib/posts/publisher_profile.g.dart @@ -0,0 +1,388 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'publisher_profile.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(publisher) +final publisherProvider = PublisherFamily._(); + +final class PublisherProvider + extends + $FunctionalProvider< + AsyncValue, + SnPublisher, + FutureOr + > + with $FutureModifier, $FutureProvider { + PublisherProvider._({ + required PublisherFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'publisherProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$publisherHash(); + + @override + String toString() { + return r'publisherProvider' + '' + '($argument)'; + } + + @$internal + @override + $FutureProviderElement $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + final argument = this.argument as String; + return publisher(ref, argument); + } + + @override + bool operator ==(Object other) { + return other is PublisherProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$publisherHash() => r'a1da21f0275421382e2882fd52c4e061c4675cf7'; + +final class PublisherFamily extends $Family + with $FunctionalFamilyOverride, String> { + PublisherFamily._() + : super( + retry: null, + name: r'publisherProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + PublisherProvider call(String uname) => + PublisherProvider._(argument: uname, from: this); + + @override + String toString() => r'publisherProvider'; +} + +@ProviderFor(publisherBadges) +final publisherBadgesProvider = PublisherBadgesFamily._(); + +final class PublisherBadgesProvider + extends + $FunctionalProvider< + AsyncValue>, + List, + FutureOr> + > + with + $FutureModifier>, + $FutureProvider> { + PublisherBadgesProvider._({ + required PublisherBadgesFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'publisherBadgesProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$publisherBadgesHash(); + + @override + String toString() { + return r'publisherBadgesProvider' + '' + '($argument)'; + } + + @$internal + @override + $FutureProviderElement> $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr> create(Ref ref) { + final argument = this.argument as String; + return publisherBadges(ref, argument); + } + + @override + bool operator ==(Object other) { + return other is PublisherBadgesProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$publisherBadgesHash() => r'26776fd6cb611953f52bdb6a7dfa004c34d5cd8e'; + +final class PublisherBadgesFamily extends $Family + with $FunctionalFamilyOverride>, String> { + PublisherBadgesFamily._() + : super( + retry: null, + name: r'publisherBadgesProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + PublisherBadgesProvider call(String pubName) => + PublisherBadgesProvider._(argument: pubName, from: this); + + @override + String toString() => r'publisherBadgesProvider'; +} + +@ProviderFor(publisherSubscriptionStatus) +final publisherSubscriptionStatusProvider = + PublisherSubscriptionStatusFamily._(); + +final class PublisherSubscriptionStatusProvider + extends + $FunctionalProvider< + AsyncValue, + SnPublisherSubscription?, + FutureOr + > + with + $FutureModifier, + $FutureProvider { + PublisherSubscriptionStatusProvider._({ + required PublisherSubscriptionStatusFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'publisherSubscriptionStatusProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$publisherSubscriptionStatusHash(); + + @override + String toString() { + return r'publisherSubscriptionStatusProvider' + '' + '($argument)'; + } + + @$internal + @override + $FutureProviderElement $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + final argument = this.argument as String; + return publisherSubscriptionStatus(ref, argument); + } + + @override + bool operator ==(Object other) { + return other is PublisherSubscriptionStatusProvider && + other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$publisherSubscriptionStatusHash() => + r'688bf38554afea9e68b2cb59c5f08c6e8dd31b62'; + +final class PublisherSubscriptionStatusFamily extends $Family + with $FunctionalFamilyOverride, String> { + PublisherSubscriptionStatusFamily._() + : super( + retry: null, + name: r'publisherSubscriptionStatusProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + PublisherSubscriptionStatusProvider call(String pubName) => + PublisherSubscriptionStatusProvider._(argument: pubName, from: this); + + @override + String toString() => r'publisherSubscriptionStatusProvider'; +} + +@ProviderFor(publisherAppbarForcegroundColor) +final publisherAppbarForcegroundColorProvider = + PublisherAppbarForcegroundColorFamily._(); + +final class PublisherAppbarForcegroundColorProvider + extends $FunctionalProvider, Color?, FutureOr> + with $FutureModifier, $FutureProvider { + PublisherAppbarForcegroundColorProvider._({ + required PublisherAppbarForcegroundColorFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'publisherAppbarForcegroundColorProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$publisherAppbarForcegroundColorHash(); + + @override + String toString() { + return r'publisherAppbarForcegroundColorProvider' + '' + '($argument)'; + } + + @$internal + @override + $FutureProviderElement $createElement($ProviderPointer pointer) => + $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + final argument = this.argument as String; + return publisherAppbarForcegroundColor(ref, argument); + } + + @override + bool operator ==(Object other) { + return other is PublisherAppbarForcegroundColorProvider && + other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$publisherAppbarForcegroundColorHash() => + r'a7c9795c68a29beb611d2c258022c9a5640f2061'; + +final class PublisherAppbarForcegroundColorFamily extends $Family + with $FunctionalFamilyOverride, String> { + PublisherAppbarForcegroundColorFamily._() + : super( + retry: null, + name: r'publisherAppbarForcegroundColorProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + PublisherAppbarForcegroundColorProvider call(String pubName) => + PublisherAppbarForcegroundColorProvider._(argument: pubName, from: this); + + @override + String toString() => r'publisherAppbarForcegroundColorProvider'; +} + +@ProviderFor(publisherHeatmap) +final publisherHeatmapProvider = PublisherHeatmapFamily._(); + +final class PublisherHeatmapProvider + extends + $FunctionalProvider< + AsyncValue, + SnHeatmap?, + FutureOr + > + with $FutureModifier, $FutureProvider { + PublisherHeatmapProvider._({ + required PublisherHeatmapFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'publisherHeatmapProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$publisherHeatmapHash(); + + @override + String toString() { + return r'publisherHeatmapProvider' + '' + '($argument)'; + } + + @$internal + @override + $FutureProviderElement $createElement($ProviderPointer pointer) => + $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + final argument = this.argument as String; + return publisherHeatmap(ref, argument); + } + + @override + bool operator ==(Object other) { + return other is PublisherHeatmapProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$publisherHeatmapHash() => r'86db275ce3861a2855b5ec35fbfef85fc47b23a6'; + +final class PublisherHeatmapFamily extends $Family + with $FunctionalFamilyOverride, String> { + PublisherHeatmapFamily._() + : super( + retry: null, + name: r'publisherHeatmapProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + PublisherHeatmapProvider call(String uname) => + PublisherHeatmapProvider._(argument: uname, from: this); + + @override + String toString() => r'publisherHeatmapProvider'; +} diff --git a/lib/screens/realm/realm_detail.dart b/lib/realms/realm/realm_detail.dart similarity index 96% rename from lib/screens/realm/realm_detail.dart rename to lib/realms/realm/realm_detail.dart index 5367c33a..cb85588e 100644 --- a/lib/screens/realm/realm_detail.dart +++ b/lib/realms/realm/realm_detail.dart @@ -1,31 +1,31 @@ import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:island/pods/post/post_list.dart'; -import 'package:island/screens/chat/chat.dart'; +import 'package:island/accounts/accounts_widgets/account/account_pfc.dart'; +import 'package:island/accounts/accounts_widgets/account/account_picker.dart'; +import 'package:island/accounts/accounts_widgets/account/status.dart'; +import 'package:island/chat/chat_widgets/chat_room_list_tile.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/posts/post/post_list.dart'; +import 'package:island/posts/posts_widgets/post/post_item.dart'; +import 'package:island/posts/posts_widgets/post/post_list.dart'; import 'package:flutter/material.dart'; -import 'package:island/models/chat.dart'; -import 'package:island/services/color.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/account/account_pfc.dart'; -import 'package:island/widgets/account/status.dart'; -import 'package:island/widgets/post/post_list.dart'; -import 'package:island/widgets/post/post_item.dart'; -import 'package:island/services/color_extraction.dart'; +import 'package:island/chat/chat_models/chat.dart'; +import 'package:island/core/services/color.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/core/services/color_extraction.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/screens/realm/realms.dart'; -import 'package:island/widgets/account/account_picker.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/realms/realm/realms.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/config.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:styled_widget/styled_widget.dart'; class _RealmPinnedPostsPageView extends HookConsumerWidget { @@ -709,7 +709,7 @@ class _RealmMemberListSheet extends HookConsumerWidget { itemBuilder: (context, index, member) { return ListTile( contentPadding: EdgeInsets.only(left: 16, right: 12), - leading: AccountPfcGestureDetector( + leading: AccountPfcRegion( uname: member.account!.name, child: ProfilePictureWidget( file: member.account!.profile.picture, diff --git a/lib/screens/realm/realm_form.dart b/lib/realms/realm/realm_form.dart similarity index 95% rename from lib/screens/realm/realm_form.dart rename to lib/realms/realm/realm_form.dart index dc8a6410..e6560f66 100644 --- a/lib/screens/realm/realm_form.dart +++ b/lib/realms/realm/realm_form.dart @@ -6,15 +6,15 @@ import 'package:go_router/go_router.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/realm/realms.dart'; -import 'package:island/services/file.dart'; -import 'package:island/services/file_uploader.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/core/services/image.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/realms/realm/realms.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/core/network.dart'; +import 'package:island/drive/drive_service.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/realm/realms.dart b/lib/realms/realm/realms.dart similarity index 92% rename from lib/screens/realm/realms.dart rename to lib/realms/realm/realms.dart index 80a07eec..363283cd 100644 --- a/lib/screens/realm/realms.dart +++ b/lib/realms/realm/realms.dart @@ -3,19 +3,19 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/realms/realms_widgets/realm/realm_list_tile.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:island/widgets/realm/realm_list_tile.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; part 'realms.g.dart'; @@ -51,7 +51,10 @@ class RealmListScreen extends HookConsumerWidget { actions: [ IconButton( icon: const Icon(Symbols.travel_explore), - onPressed: () => context.pushNamed('universalSearch', queryParameters: {'tab': 'realms'}), + onPressed: () => context.pushNamed( + 'universalSearch', + queryParameters: {'tab': 'realms'}, + ), ), IconButton( icon: Badge( diff --git a/lib/screens/realm/realms.g.dart b/lib/realms/realm/realms.g.dart similarity index 100% rename from lib/screens/realm/realms.g.dart rename to lib/realms/realm/realms.g.dart diff --git a/lib/models/realm.dart b/lib/realms/realms_models/realm.dart similarity index 91% rename from lib/models/realm.dart rename to lib/realms/realms_models/realm.dart index 6141dbc5..5a86dc80 100644 --- a/lib/models/realm.dart +++ b/lib/realms/realms_models/realm.dart @@ -1,6 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/account.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/accounts/accounts_models/account.dart'; part 'realm.freezed.dart'; part 'realm.g.dart'; diff --git a/lib/models/realm.freezed.dart b/lib/realms/realms_models/realm.freezed.dart similarity index 100% rename from lib/models/realm.freezed.dart rename to lib/realms/realms_models/realm.freezed.dart diff --git a/lib/models/realm.g.dart b/lib/realms/realms_models/realm.g.dart similarity index 100% rename from lib/models/realm.g.dart rename to lib/realms/realms_models/realm.g.dart diff --git a/lib/widgets/realm/realm_card.dart b/lib/realms/realms_widgets/realm/realm_card.dart similarity index 96% rename from lib/widgets/realm/realm_card.dart rename to lib/realms/realms_widgets/realm/realm_card.dart index 674db6b8..cbbdd7c9 100644 --- a/lib/widgets/realm/realm_card.dart +++ b/lib/realms/realms_widgets/realm/realm_card.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; class RealmDiscoveryCard extends ConsumerWidget { diff --git a/lib/widgets/realm/realm_list.dart b/lib/realms/realms_widgets/realm/realm_list.dart similarity index 87% rename from lib/widgets/realm/realm_list.dart rename to lib/realms/realms_widgets/realm/realm_list.dart index 4ad55f97..e7cf20fd 100644 --- a/lib/widgets/realm/realm_list.dart +++ b/lib/realms/realms_widgets/realm/realm_list.dart @@ -2,11 +2,11 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; -import 'package:island/widgets/realm/realm_list_tile.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/core/network.dart'; +import 'package:island/realms/realms_widgets/realm/realm_list_tile.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:styled_widget/styled_widget.dart'; final realmListNotifierProvider = AsyncNotifierProvider.autoDispose.family( diff --git a/lib/widgets/realm/realm_list_tile.dart b/lib/realms/realms_widgets/realm/realm_list_tile.dart similarity index 95% rename from lib/widgets/realm/realm_list_tile.dart rename to lib/realms/realms_widgets/realm/realm_list_tile.dart index c723a78e..866dc947 100644 --- a/lib/widgets/realm/realm_list_tile.dart +++ b/lib/realms/realms_widgets/realm/realm_list_tile.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/realm/realm_selection_dropdown.dart b/lib/realms/realms_widgets/realm/realm_selection_dropdown.dart similarity index 94% rename from lib/widgets/realm/realm_selection_dropdown.dart rename to lib/realms/realms_widgets/realm/realm_selection_dropdown.dart index d8099028..3bf64aa6 100644 --- a/lib/widgets/realm/realm_selection_dropdown.dart +++ b/lib/realms/realms_widgets/realm/realm_selection_dropdown.dart @@ -1,8 +1,8 @@ import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; class RealmSelectionDropdown extends StatelessWidget { diff --git a/lib/widgets/realm/realm_tile.dart b/lib/realms/realms_widgets/realm/realm_tile.dart similarity index 82% rename from lib/widgets/realm/realm_tile.dart rename to lib/realms/realms_widgets/realm/realm_tile.dart index 6239c4de..7597b120 100644 --- a/lib/widgets/realm/realm_tile.dart +++ b/lib/realms/realms_widgets/realm/realm_tile.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/realm.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/realms/realms_models/realm.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; class RealmTile extends HookConsumerWidget { final SnRealm realm; diff --git a/lib/screens/reports/report_detail.dart b/lib/reports/reports/report_detail.dart similarity index 92% rename from lib/screens/reports/report_detail.dart rename to lib/reports/reports/report_detail.dart index d309de2c..5d1ab3a1 100644 --- a/lib/screens/reports/report_detail.dart +++ b/lib/reports/reports/report_detail.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/abuse_report.dart'; -import 'package:island/models/abuse_report_type.dart'; -import 'package:island/services/abuse_report_service.dart'; -import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/accounts/accounts_models/abuse_report.dart'; +import 'package:island/accounts/accounts_models/abuse_report_type.dart'; +import 'package:island/accounts/abuse_report_service.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; import 'package:styled_widget/styled_widget.dart'; class AbuseReportDetailScreen extends ConsumerStatefulWidget { diff --git a/lib/screens/reports/report_list.dart b/lib/reports/reports/report_list.dart similarity index 87% rename from lib/screens/reports/report_list.dart rename to lib/reports/reports/report_list.dart index 664cfcd6..0b7f381a 100644 --- a/lib/screens/reports/report_list.dart +++ b/lib/reports/reports/report_list.dart @@ -2,12 +2,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/abuse_report.dart'; -import 'package:island/models/abuse_report_type.dart'; -import 'package:island/services/abuse_report_service.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/safety/abuse_report_helper.dart'; +import 'package:island/accounts/accounts_models/abuse_report.dart'; +import 'package:island/accounts/accounts_models/abuse_report_type.dart'; +import 'package:island/accounts/abuse_report_service.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/reports/reports_widgets/safety/abuse_report_helper.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; class AbuseReportListScreen extends ConsumerStatefulWidget { const AbuseReportListScreen({super.key}); @@ -58,7 +58,10 @@ class _AbuseReportListScreenState extends ConsumerState { ), child: InkWell( onTap: () { - context.pushNamed('reportDetail', pathParameters: {'id': report.id}); + context.pushNamed( + 'reportDetail', + pathParameters: {'id': report.id}, + ); }, child: Padding( padding: const EdgeInsets.all(16.0), @@ -125,14 +128,12 @@ class _AbuseReportListScreenState extends ConsumerState { report.resolvedAt != null ? 'Resolved' : 'Unresolved', - style: Theme.of( - context, - ).textTheme.bodyMedium?.copyWith( - color: - report.resolvedAt != null + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith( + color: report.resolvedAt != null ? Colors.green : Colors.orange, - ), + ), ), ], ), diff --git a/lib/widgets/safety/abuse_report_helper.dart b/lib/reports/reports_widgets/safety/abuse_report_helper.dart similarity index 72% rename from lib/widgets/safety/abuse_report_helper.dart rename to lib/reports/reports_widgets/safety/abuse_report_helper.dart index 99945d64..23174659 100644 --- a/lib/widgets/safety/abuse_report_helper.dart +++ b/lib/reports/reports_widgets/safety/abuse_report_helper.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:island/widgets/safety/abuse_report_sheet.dart'; +import 'package:island/reports/reports_widgets/safety/abuse_report_sheet.dart'; /// Helper function to show the safety report sheet /// @@ -15,10 +15,9 @@ Future showAbuseReportSheet( context: context, isScrollControlled: true, useRootNavigator: true, - builder: - (context) => AbuseReportSheet( - resourceIdentifier: resourceIdentifier, - initialReason: initialReason, - ), + builder: (context) => AbuseReportSheet( + resourceIdentifier: resourceIdentifier, + initialReason: initialReason, + ), ); } diff --git a/lib/widgets/safety/abuse_report_sheet.dart b/lib/reports/reports_widgets/safety/abuse_report_sheet.dart similarity index 81% rename from lib/widgets/safety/abuse_report_sheet.dart rename to lib/reports/reports_widgets/safety/abuse_report_sheet.dart index 3b39a90c..0be4ee24 100644 --- a/lib/widgets/safety/abuse_report_sheet.dart +++ b/lib/reports/reports_widgets/safety/abuse_report_sheet.dart @@ -3,9 +3,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:material_symbols_icons/symbols.dart'; class AbuseReportSheet extends HookConsumerWidget { @@ -55,25 +55,24 @@ class AbuseReportSheet extends HookConsumerWidget { Navigator.of(context).pop(); showDialog( context: context, - builder: - (contextDialog) => AlertDialog( - icon: const Icon( - Icons.check_circle, - color: Colors.green, - size: 36, - ), - title: Text('abuseReportSuccessTitle'.tr()), - content: Text('abuseReportSuccess'.tr()), - actions: [ - TextButton( - onPressed: () { - Navigator.of(contextDialog).pop(); - Navigator.of(context).pop(); - }, - child: const Text('OK'), - ), - ], + builder: (contextDialog) => AlertDialog( + icon: const Icon( + Icons.check_circle, + color: Colors.green, + size: 36, + ), + title: Text('abuseReportSuccessTitle'.tr()), + content: Text('abuseReportSuccess'.tr()), + actions: [ + TextButton( + onPressed: () { + Navigator.of(contextDialog).pop(); + Navigator.of(context).pop(); + }, + child: const Text('OK'), ), + ], + ), ); } } catch (err) { @@ -165,14 +164,13 @@ class AbuseReportSheet extends HookConsumerWidget { width: double.infinity, child: FilledButton( onPressed: isSubmitting.value ? null : submitReport, - child: - isSubmitting.value - ? const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : Text('abuseReportSubmit'.tr()), + child: isSubmitting.value + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : Text('abuseReportSubmit'.tr()), ), ), const Gap(16), diff --git a/lib/route.dart b/lib/route.dart index 83ae518f..fd926b3a 100644 --- a/lib/route.dart +++ b/lib/route.dart @@ -5,64 +5,64 @@ import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/screens/about.dart'; -import 'package:island/screens/dashboard/dash.dart'; -import 'package:island/screens/developers/app_detail.dart'; -import 'package:island/screens/developers/bot_detail.dart'; -import 'package:island/screens/developers/hub.dart'; -import 'package:island/screens/developers/edit_project.dart'; -import 'package:island/screens/developers/new_project.dart'; -import 'package:island/screens/discovery/articles.dart'; -import 'package:island/models/file.dart'; -import 'package:island/screens/files/file_list.dart'; -import 'package:island/screens/files/file_detail.dart'; -import 'package:island/screens/posts/post_categories_list.dart'; -import 'package:island/screens/posts/post_category_detail.dart'; -import 'package:island/screens/search.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/app_wrapper.dart'; -import 'package:island/screens/tabs.dart'; -import 'package:island/screens/explore.dart'; -import 'package:island/screens/discovery/article_detail.dart'; -import 'package:island/screens/account.dart'; -import 'package:island/screens/wallet.dart'; -import 'package:island/screens/account/relationship.dart'; -import 'package:island/screens/account/profile.dart'; -import 'package:island/screens/account/me/profile_update.dart'; -import 'package:island/screens/account/leveling.dart'; -import 'package:island/screens/account/me/account_settings.dart'; -import 'package:island/screens/chat/chat.dart'; -import 'package:island/screens/chat/room.dart'; -import 'package:island/screens/chat/room_detail.dart'; -import 'package:island/screens/chat/search_messages.dart'; -import 'package:island/screens/thought/think.dart'; -import 'package:island/screens/creators/hub.dart'; -import 'package:island/screens/creators/posts/post_manage_list.dart'; -import 'package:island/screens/creators/stickers/stickers.dart'; -import 'package:island/screens/stickers/sticker_marketplace.dart'; -import 'package:island/screens/stickers/pack_detail.dart'; -import 'package:island/screens/discovery/feeds/feed_marketplace.dart'; -import 'package:island/screens/discovery/feeds/feed_detail.dart'; -import 'package:island/screens/creators/poll/poll_list.dart'; -import 'package:island/screens/creators/sites/site_detail.dart'; -import 'package:island/screens/creators/sites/site_list.dart'; -import 'package:island/screens/creators/webfeed/webfeed_list.dart'; -import 'package:island/screens/fitness_activity.dart'; -import 'package:island/screens/posts/compose.dart'; -import 'package:island/screens/posts/compose_article.dart'; -import 'package:island/screens/posts/post_detail.dart'; -import 'package:island/screens/posts/publisher_profile.dart'; -import 'package:island/screens/auth/login.dart'; -import 'package:island/screens/auth/create_account.dart'; -import 'package:island/screens/settings.dart'; -import 'package:island/screens/realm/realms.dart'; -import 'package:island/screens/realm/realm_form.dart'; -import 'package:island/screens/realm/realm_detail.dart'; -import 'package:island/screens/discovery/realms.dart'; -import 'package:island/screens/reports/report_detail.dart'; -import 'package:island/screens/reports/report_list.dart'; +import 'package:island/accounts/account/profile.dart'; +import 'package:island/chat/chat_widgets/chat_detail_screen.dart'; +import 'package:island/chat/chat_widgets/chat_list_screen.dart'; +import 'package:island/chat/chat_widgets/chat_room_screen.dart'; +import 'package:island/chat/chat_widgets/chat_search_screen.dart'; +import 'package:island/creators/creators/hub.dart'; +import 'package:island/creators/creators/poll/poll_list.dart'; +import 'package:island/creators/creators/posts/post_manage_list.dart'; +import 'package:island/creators/creators/sites/site_detail.dart'; +import 'package:island/creators/creators/sites/site_list.dart'; +import 'package:island/creators/creators/stickers/stickers.dart'; +import 'package:island/creators/creators/webfeed/webfeed_list.dart'; +import 'package:island/developers/developers/app_detail.dart'; +import 'package:island/developers/developers/bot_detail.dart'; +import 'package:island/developers/developers/edit_project.dart'; +import 'package:island/developers/developers/hub.dart'; +import 'package:island/developers/developers/new_project.dart'; +import 'package:island/discovery/discovery/article_detail.dart'; +import 'package:island/discovery/discovery/articles.dart'; +import 'package:island/discovery/discovery/feeds/feed_detail.dart'; +import 'package:island/discovery/discovery/feeds/feed_marketplace.dart'; +import 'package:island/discovery/discovery/realms.dart'; +import 'package:island/drive/files/file_detail.dart'; +import 'package:island/drive/files/file_list.dart'; +import 'package:island/posts/posts/post_categories_list.dart'; +import 'package:island/posts/posts/post_category_detail.dart'; +import 'package:island/posts/posts/post_detail.dart'; +import 'package:island/posts/posts_widgets/post/post_shuffle.dart'; +import 'package:island/realms/realm/realm_detail.dart'; +import 'package:island/realms/realm/realm_form.dart'; +import 'package:island/realms/realm/realms.dart'; +import 'package:island/reports/reports/report_detail.dart'; +import 'package:island/reports/reports/report_list.dart'; +import 'package:island/settings/about.dart'; +import 'package:island/settings/dashboard/dash.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/discovery/search.dart'; +import 'package:island/settings/settings.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/shared/widgets/app_wrapper.dart'; +import 'package:island/settings/tabs_screen.dart'; +import 'package:island/discovery/explore.dart'; +import 'package:island/accounts/accounts_screen.dart'; +import 'package:island/accounts/account/relationship.dart'; +import 'package:island/accounts/account/me/profile_update.dart'; +import 'package:island/accounts/account/leveling.dart'; +import 'package:island/accounts/account/me/account_settings.dart'; +import 'package:island/fitness/fitness_screen.dart'; +import 'package:island/posts/compose.dart'; +import 'package:island/posts/compose_article.dart'; +import 'package:island/posts/publisher_profile.dart'; +import 'package:island/auth/login.dart'; +import 'package:island/auth/create_account.dart'; +import 'package:island/stickers/stickers/pack_detail.dart'; +import 'package:island/stickers/stickers/sticker_marketplace.dart'; import 'package:island/talker.dart'; -import 'package:island/widgets/post/post_shuffle.dart'; +import 'package:island/thought/thought/think.dart'; +import 'package:island/wallet/wallet.dart'; import 'package:talker_flutter/talker_flutter.dart'; // Shell route keys for nested navigation diff --git a/lib/screens/activitypub/search.dart b/lib/screens/activitypub/search.dart deleted file mode 100644 index 57ce931d..00000000 --- a/lib/screens/activitypub/search.dart +++ /dev/null @@ -1,180 +0,0 @@ -import 'dart:async'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:gap/gap.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/activitypub.dart'; -import 'package:island/services/activitypub_service.dart'; -import 'package:island/widgets/activitypub/actor_list_item.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; -import 'package:material_symbols_icons/symbols.dart'; -import 'package:styled_widget/styled_widget.dart'; - -class ApSearchScreen extends HookConsumerWidget { - const ApSearchScreen({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final searchController = useTextEditingController(); - final debounce = useMemoized(() => const Duration(milliseconds: 500)); - final debounceTimer = useRef(null); - final searchResults = useState>([]); - final isSearching = useState(false); - - useEffect(() { - return () { - searchController.dispose(); - debounceTimer.value?.cancel(); - }; - }, []); - - Future performSearch(String query) async { - if (query.trim().isEmpty) { - searchResults.value = []; - return; - } - - isSearching.value = true; - try { - final service = ref.read(activityPubServiceProvider); - final results = await service.searchUsers(query); - searchResults.value = results; - } catch (err) { - showErrorAlert(err); - } finally { - isSearching.value = false; - } - } - - void onSearchChanged(String query) { - if (debounceTimer.value?.isActive ?? false) { - debounceTimer.value!.cancel(); - } - debounceTimer.value = Timer(debounce, () { - performSearch(query); - }); - } - - void updateActorIsFollowing(String actorId, bool isFollowing) { - searchResults.value = searchResults.value - .map( - (a) => a.id == actorId ? a.copyWith(isFollowing: isFollowing) : a, - ) - .toList(); - } - - Future handleFollow(SnActivityPubActor actor) async { - try { - updateActorIsFollowing(actor.id, true); - final service = ref.read(activityPubServiceProvider); - await service.followRemoteUser(actor.uri); - showSnackBar( - 'followedUser'.tr( - args: [ - '${actor.username?.isNotEmpty ?? false ? actor.username : actor.displayName}', - ], - ), - ); - } catch (err) { - showErrorAlert(err); - updateActorIsFollowing(actor.id, false); - } - } - - Future handleUnfollow(SnActivityPubActor actor) async { - try { - updateActorIsFollowing(actor.id, false); - final service = ref.read(activityPubServiceProvider); - await service.unfollowRemoteUser(actor.uri); - showSnackBar( - 'unfollowedUser'.tr( - args: [ - '${actor.username?.isNotEmpty ?? false ? actor.username : actor.displayName}', - ], - ), - ); - } catch (err) { - showErrorAlert(err); - updateActorIsFollowing(actor.id, true); - } - } - - return AppScaffold( - isNoBackground: false, - appBar: AppBar(title: Text('searchFediverse'.tr()), elevation: 0), - body: Column( - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: SearchBar( - controller: searchController, - hintText: 'searchFediverseHint'.tr( - args: ['@username@instance.com'], - ), - leading: const Icon(Symbols.search).padding(horizontal: 24), - onChanged: onSearchChanged, - onSubmitted: (value) { - onSearchChanged(value); - performSearch(value); - }, - ), - ), - Expanded( - child: isSearching.value - ? const Center(child: CircularProgressIndicator()) - : searchResults.value.isEmpty - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Symbols.search, - size: 64, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - const SizedBox(height: 16), - if (searchController.text.isEmpty) - Text( - 'searchFediverseEmpty'.tr(), - style: Theme.of(context).textTheme.titleMedium, - ) - else - Text( - 'searchFediverseNoResults'.tr(), - style: Theme.of(context).textTheme.titleMedium, - ), - ], - ), - ) - : ExtendedRefreshIndicator( - onRefresh: () => performSearch(searchController.text), - child: ListView.separated( - padding: const EdgeInsets.symmetric(vertical: 8), - itemCount: searchResults.value.length, - separatorBuilder: (context, index) => const Gap(8), - itemBuilder: (context, index) { - final actor = searchResults.value[index]; - return Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 560), - child: ApActorListItem( - actor: actor, - isFollowing: actor.isFollowing ?? false, - isLoading: false, - onFollow: () => handleFollow(actor), - onUnfollow: () => handleUnfollow(actor), - ), - ), - ); - }, - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/screens/chat/public_room_preview.dart b/lib/screens/chat/public_room_preview.dart deleted file mode 100644 index 13010783..00000000 --- a/lib/screens/chat/public_room_preview.dart +++ /dev/null @@ -1,215 +0,0 @@ -import "package:flutter/material.dart"; -import "package:flutter_hooks/flutter_hooks.dart"; -import "package:gap/gap.dart"; -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:island/database/message.dart"; -import "package:island/pods/chat/chat_room.dart"; -import "package:island/widgets/content/cloud_files.dart"; -import "package:super_sliver_list/super_sliver_list.dart"; -import "package:easy_localization/easy_localization.dart"; -import "package:go_router/go_router.dart"; -import "package:material_symbols_icons/symbols.dart"; -import "package:styled_widget/styled_widget.dart"; -import "package:island/models/chat.dart"; -import "package:island/widgets/alert.dart"; -import "package:island/widgets/app_scaffold.dart"; -import "package:island/widgets/chat/message_item.dart"; -import "package:island/widgets/response.dart"; -import "package:island/pods/network.dart"; -import "package:island/services/responsive.dart"; -import "package:island/pods/chat/messages_notifier.dart"; - -class PublicRoomPreview extends HookConsumerWidget { - final String id; - final SnChatRoom room; - - const PublicRoomPreview({super.key, required this.id, required this.room}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final messages = ref.watch(messagesProvider(id)); - final messagesNotifier = ref.read(messagesProvider(id).notifier); - final scrollController = useScrollController(); - - final listController = useMemoized(() => ListController(), []); - - var isLoading = false; - - // Add scroll listener for pagination - useEffect(() { - void onScroll() { - if (scrollController.position.pixels >= - scrollController.position.maxScrollExtent - 200) { - if (isLoading) return; - isLoading = true; - messagesNotifier.loadMore().then((_) => isLoading = false); - } - } - - scrollController.addListener(onScroll); - return () => scrollController.removeListener(onScroll); - }, [scrollController]); - - Widget chatMessageListWidget(List messageList) => - SuperListView.builder( - listController: listController, - padding: EdgeInsets.symmetric(vertical: 16), - controller: scrollController, - reverse: true, // Show newest messages at the bottom - itemCount: messageList.length, - findChildIndexCallback: (key) { - final valueKey = key as ValueKey; - final messageId = valueKey.value as String; - return messageList.indexWhere((m) => m.id == messageId); - }, - extentEstimation: (_, _) => 40, - itemBuilder: (context, index) { - final message = messageList[index]; - final nextMessage = index < messageList.length - 1 - ? messageList[index + 1] - : null; - final isLastInGroup = - nextMessage == null || - nextMessage.senderId != message.senderId || - nextMessage.createdAt - .difference(message.createdAt) - .inMinutes - .abs() > - 3; - - return MessageItem( - message: message, - isCurrentUser: false, // User is not a member, so not current user - onAction: null, // No actions allowed in preview mode - onJump: (_) {}, // No jump functionality in preview - progress: null, - showAvatar: isLastInGroup, - ); - }, - ); - - final compactHeader = isWideScreen(context); - - Widget comfortHeaderWidget() => Column( - spacing: 4, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - height: 26, - width: 26, - child: (room.type == 1 && room.picture == null) - ? SplitAvatarWidget( - files: room.members! - .map((e) => e.account.profile.picture) - .toList(), - ) - : room.picture != null - ? ProfilePictureWidget( - file: room.picture, - fallbackIcon: Symbols.chat, - ) - : CircleAvatar( - child: Text( - room.name![0].toUpperCase(), - style: const TextStyle(fontSize: 12), - ), - ), - ), - Text( - (room.type == 1 && room.name == null) - ? room.members!.map((e) => e.account.nick).join(', ') - : room.name!, - ).fontSize(15), - ], - ); - - Widget compactHeaderWidget() => Row( - spacing: 8, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - height: 26, - width: 26, - child: (room.type == 1 && room.picture == null) - ? SplitAvatarWidget( - files: room.members! - .map((e) => e.account.profile.picture) - .toList(), - ) - : room.picture != null - ? ProfilePictureWidget( - file: room.picture, - fallbackIcon: Symbols.chat, - ) - : CircleAvatar( - child: Text( - room.name![0].toUpperCase(), - style: const TextStyle(fontSize: 12), - ), - ), - ), - Text( - (room.type == 1 && room.name == null) - ? room.members!.map((e) => e.account.nick).join(', ') - : room.name!, - ).fontSize(19), - ], - ); - - return AppScaffold( - appBar: AppBar( - leading: !compactHeader ? const Center(child: PageBackButton()) : null, - automaticallyImplyLeading: false, - toolbarHeight: compactHeader ? null : 64, - title: compactHeader ? compactHeaderWidget() : comfortHeaderWidget(), - actions: [ - IconButton( - icon: const Icon(Icons.more_vert), - onPressed: () { - context.pushNamed('chatDetail', pathParameters: {'id': id}); - }, - ), - const Gap(8), - ], - ), - body: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: messages.when( - data: (messageList) => messageList.isEmpty - ? Center(child: Text('No messages yet'.tr())) - : chatMessageListWidget(messageList), - loading: () => const Center(child: CircularProgressIndicator()), - error: (error, _) => ResponseErrorWidget( - error: error, - onRetry: () => messagesNotifier.loadInitial(), - ), - ), - ), - // Join button at the bottom for public rooms - Container( - padding: const EdgeInsets.all(16), - child: FilledButton.tonalIcon( - onPressed: () async { - try { - showLoadingModal(context); - final apiClient = ref.read(apiClientProvider); - await apiClient.post('/messager/chat/${room.id}/members/me'); - ref.invalidate(chatRoomIdentityProvider(id)); - } catch (err) { - showErrorAlert(err); - } finally { - if (context.mounted) hideLoadingModal(context); - } - }, - label: Text('chatJoin').tr(), - icon: const Icon(Icons.add), - ), - ), - ], - ), - ); - } -} diff --git a/lib/services/file_download.dart b/lib/services/file_download.dart deleted file mode 100644 index b77a8360..00000000 --- a/lib/services/file_download.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'dart:io'; - -import 'package:file_saver/file_saver.dart'; -import 'package:flutter/foundation.dart'; -import 'package:gal/gal.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/drive_task.dart'; -import 'package:island/models/file.dart'; -import 'package:island/pods/drive/upload_tasks.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:path/path.dart' as p; -import 'package:path_provider/path_provider.dart'; - -class FileDownloadService { - final WidgetRef ref; - - FileDownloadService(this.ref); - - String _getFileExtension(SnCloudFile item) { - var extName = p.extension(item.name).trim(); - if (extName.isEmpty) { - extName = item.mimeType?.split('/').lastOrNull ?? 'jpeg'; - } - return extName.replaceFirst('.', ''); - } - - String _getFileName(SnCloudFile item, String extName) { - return item.name.isEmpty ? '${item.id}.$extName' : item.name; - } - - Future _downloadToTemp(SnCloudFile item, String extName) async { - final client = ref.read(apiClientProvider); - final tempDir = await getTemporaryDirectory(); - final filePath = '${tempDir.path}/${item.id}.$extName'; - - await client.download( - '/drive/files/${item.id}', - filePath, - queryParameters: {'original': true}, - ); - - return filePath; - } - - Future saveToGallery(SnCloudFile item) async { - try { - showSnackBar('Saving image...'); - - final extName = _getFileExtension(item); - final filePath = await _downloadToTemp(item, extName); - - if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { - await Gal.putImage(filePath, album: 'Solar Network'); - showSnackBar('Image saved to gallery'); - } else { - await FileSaver.instance.saveFile( - name: _getFileName(item, extName), - file: File(filePath), - ); - showSnackBar('Image saved to downloads'); - } - } catch (e) { - showErrorAlert(e); - } - } - - Future downloadFile(SnCloudFile item) async { - try { - showSnackBar('Downloading file...'); - - final extName = _getFileExtension(item); - final filePath = await _downloadToTemp(item, extName); - - await FileSaver.instance.saveFile( - name: _getFileName(item, extName), - file: File(filePath), - ); - showSnackBar('File saved to downloads'); - } catch (e) { - showErrorAlert(e); - } - } - - Future downloadWithProgress( - SnCloudFile item, { - void Function(int received, int total)? onProgress, - }) async { - final taskNotifier = ref.read(uploadTasksProvider.notifier); - final taskId = taskNotifier.addLocalDownloadTask(item); - - try { - showSnackBar('Downloading file...'); - - final client = ref.read(apiClientProvider); - final extName = _getFileExtension(item); - final tempDir = await getTemporaryDirectory(); - final filePath = '${tempDir.path}/${item.id}.$extName'; - - await client.download( - '/drive/files/${item.id}', - filePath, - queryParameters: {'original': true}, - onReceiveProgress: (count, total) { - onProgress?.call(count, total); - if (total > 0) { - taskNotifier.updateDownloadProgress(taskId, count, total); - taskNotifier.updateTransmissionProgress(taskId, count / total); - } - }, - ); - - await FileSaver.instance.saveFile( - name: _getFileName(item, extName), - file: File(filePath), - ); - taskNotifier.updateTaskStatus(taskId, DriveTaskStatus.completed); - showSnackBar('File saved to downloads'); - } catch (e) { - taskNotifier.updateTaskStatus( - taskId, - DriveTaskStatus.failed, - errorMessage: e.toString(), - ); - showErrorAlert(e); - } - } -} diff --git a/lib/screens/about.dart b/lib/settings/about.dart similarity index 98% rename from lib/screens/about.dart rename to lib/settings/about.dart index e063e47c..c03b7a22 100644 --- a/lib/screens/about.dart +++ b/lib/settings/about.dart @@ -6,9 +6,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/services/udid.dart' as udid; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/core/services/udid.dart' as udid; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/dashboard/dash.dart b/lib/settings/dashboard/dash.dart similarity index 95% rename from lib/screens/dashboard/dash.dart rename to lib/settings/dashboard/dash.dart index fe38e1b9..2261e537 100644 --- a/lib/screens/dashboard/dash.dart +++ b/lib/settings/dashboard/dash.dart @@ -1,4 +1,5 @@ import 'dart:math' as math; +import 'dart:async'; import 'package:desktop_drop/desktop_drop.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -8,31 +9,29 @@ import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/chat/chat_room.dart'; -import 'package:island/pods/chat/chat_summary.dart'; -import 'package:island/pods/event_calendar.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/screens/chat/chat.dart'; -import 'package:island/services/event_bus.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/account/account_name.dart'; -import 'package:island/widgets/account/fortune_graph.dart'; -import 'package:island/widgets/account/friends_overview.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/notification_tile.dart'; -import 'package:island/widgets/post/post_featured.dart'; -import 'package:island/widgets/check_in.dart'; -import 'package:island/screens/auth/login_modal.dart'; -import 'package:island/models/activity.dart'; -import 'package:island/screens/notification.dart'; +import 'package:island/accounts/accounts_widgets/account/account_name.dart'; +import 'package:island/accounts/accounts_widgets/account/fortune_graph.dart'; +import 'package:island/accounts/accounts_widgets/account/friends_overview.dart'; +import 'package:island/chat/chat_pod/chat_room.dart'; +import 'package:island/chat/chat_pod/chat_summary.dart'; +import 'package:island/accounts/event_calendar.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/chat/chat_widgets/chat_room_list_tile.dart'; +import 'package:island/core/services/event_bus.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/notifications/notification.dart'; +import 'package:island/posts/posts_widgets/post/post_featured.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/notifications/notification_tile.dart'; +import 'package:island/accounts/check_in.dart'; +import 'package:island/auth/login_modal.dart'; +import 'package:island/core/models/activity.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:slide_countdown/slide_countdown.dart'; -import 'package:island/widgets/share/share_sheet.dart'; -import 'dart:async'; - +import 'package:island/core/widgets/share/share_sheet.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:island/screens/dashboard/dash_customize.dart'; -import 'package:island/pods/config.dart'; +import 'package:island/settings/dashboard/dash_customize.dart'; +import 'package:island/core/config.dart'; class DashboardScreen extends HookConsumerWidget { const DashboardScreen({super.key}); diff --git a/lib/screens/dashboard/dash_customize.dart b/lib/settings/dashboard/dash_customize.dart similarity index 98% rename from lib/screens/dashboard/dash_customize.dart rename to lib/settings/dashboard/dash_customize.dart index df43517a..b54ffa4e 100644 --- a/lib/screens/dashboard/dash_customize.dart +++ b/lib/settings/dashboard/dash_customize.dart @@ -3,9 +3,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/core/config.dart'; +import 'package:island/shared/widgets/alert.dart'; import 'package:styled_widget/styled_widget.dart'; class DashboardCustomizationSheet extends HookConsumerWidget { diff --git a/lib/screens/settings.dart b/lib/settings/settings.dart similarity index 98% rename from lib/screens/settings.dart rename to lib/settings/settings.dart index b873c75e..0dc01ebe 100644 --- a/lib/screens/settings.dart +++ b/lib/settings/settings.dart @@ -11,17 +11,17 @@ import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/services/color_extraction.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/core/services/color_extraction.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:path_provider/path_provider.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/drive/file_pool.dart'; +import 'package:island/core/config.dart'; +import 'package:island/drive/drive/file_pool.dart'; class SettingsScreen extends HookConsumerWidget { const SettingsScreen({super.key}); diff --git a/lib/screens/tabs.dart b/lib/settings/tabs_screen.dart similarity index 95% rename from lib/screens/tabs.dart rename to lib/settings/tabs_screen.dart index 6cbe38d0..15dfd457 100644 --- a/lib/screens/tabs.dart +++ b/lib/settings/tabs_screen.dart @@ -6,13 +6,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/screens/notification.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/navigation/conditional_bottom_nav.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/core/navigation/conditional_bottom_nav.dart'; +import 'package:island/notifications/notification.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:island/pods/chat/chat_summary.dart'; +import 'package:island/chat/chat_pod/chat_summary.dart'; import 'package:styled_widget/styled_widget.dart'; final currentRouteProvider = NotifierProvider( diff --git a/lib/screens/tray_manager.dart b/lib/settings/tray_manager.dart similarity index 100% rename from lib/screens/tray_manager.dart rename to lib/settings/tray_manager.dart diff --git a/lib/widgets/alert.dart b/lib/shared/widgets/alert.dart similarity index 98% rename from lib/widgets/alert.dart rename to lib/shared/widgets/alert.dart index af58d266..43770d7d 100644 --- a/lib/widgets/alert.dart +++ b/lib/shared/widgets/alert.dart @@ -6,9 +6,9 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/main.dart'; -import 'package:island/models/account.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/notification.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/notification.dart'; import 'package:island/talker.dart'; import 'package:just_audio/just_audio.dart'; import 'package:material_symbols_icons/symbols.dart'; diff --git a/lib/widgets/app_notification.dart b/lib/shared/widgets/app_notification.dart similarity index 96% rename from lib/widgets/app_notification.dart rename to lib/shared/widgets/app_notification.dart index 4d4d6c69..34d19c03 100644 --- a/lib/widgets/app_notification.dart +++ b/lib/shared/widgets/app_notification.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; +import 'package:island/accounts/accounts_models/account.dart'; import 'package:island/route.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher_string.dart'; diff --git a/lib/widgets/app_scaffold.dart b/lib/shared/widgets/app_scaffold.dart similarity index 96% rename from lib/widgets/app_scaffold.dart rename to lib/shared/widgets/app_scaffold.dart index 231a9c69..68b34b79 100644 --- a/lib/widgets/app_scaffold.dart +++ b/lib/shared/widgets/app_scaffold.dart @@ -9,16 +9,16 @@ import 'package:go_router/go_router.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hotkey_manager/hotkey_manager.dart'; -import 'package:island/pods/config.dart'; +import 'package:island/command_palette/palette.dart'; +import 'package:island/core/config.dart'; import 'package:island/route.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/pods/websocket.dart'; -import 'package:island/services/event_bus.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/cmp/pattle.dart'; -import 'package:island/widgets/notification_overlay.dart'; -import 'package:island/widgets/task_overlay.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/core/websocket.dart'; +import 'package:island/core/services/event_bus.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/notifications/notification_overlay.dart'; +import 'package:island/shared/widgets/task_overlay.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shake/shake.dart'; @@ -264,7 +264,7 @@ class WindowScaffold extends HookConsumerWidget { const TaskOverlay(), const NotificationOverlay(), if (showPalette.value) - CommandPattleWidget(onDismiss: () => showPalette.value = false), + CommandPaletteWidget(onDismiss: () => showPalette.value = false), ], ), ); @@ -278,7 +278,7 @@ class WindowScaffold extends HookConsumerWidget { const TaskOverlay(), const NotificationOverlay(), if (showPalette.value) - CommandPattleWidget(onDismiss: () => showPalette.value = false), + CommandPaletteWidget(onDismiss: () => showPalette.value = false), ], ); } diff --git a/lib/widgets/app_wrapper.dart b/lib/shared/widgets/app_wrapper.dart similarity index 92% rename from lib/widgets/app_wrapper.dart rename to lib/shared/widgets/app_wrapper.dart index c5ee9be3..ff605cd6 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/shared/widgets/app_wrapper.dart @@ -4,24 +4,24 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:in_app_review/in_app_review.dart'; +import 'package:island/auth/web_auth/web_auth_providers.dart'; +import 'package:island/notifications/notification.dart'; +import 'package:island/thought/thought/think_sheet.dart'; import 'package:protocol_handler/protocol_handler.dart'; -import 'package:island/pods/activity/activity_rpc.dart'; -import 'package:island/pods/config.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/websocket.dart'; +import 'package:island/activity/activity_rpc.dart'; +import 'package:island/core/config.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/websocket.dart'; import 'package:island/route.dart'; -import 'package:island/screens/auth/login_content.dart'; -import 'package:island/screens/tray_manager.dart'; -import 'package:island/pods/web_auth/web_auth_providers.dart'; -import 'package:island/services/notify.dart'; -import 'package:island/services/sharing_intent.dart'; -import 'package:island/services/update_service.dart'; -import 'package:island/widgets/content/network_status_sheet.dart'; -import 'package:island/widgets/tour/tour.dart'; -import 'package:island/widgets/post/compose_sheet.dart'; -import 'package:island/screens/notification.dart'; -import 'package:island/screens/thought/think_sheet.dart'; -import 'package:island/services/event_bus.dart'; +import 'package:island/auth/login_content.dart'; +import 'package:island/settings/tray_manager.dart'; +import 'package:island/core/services/notify.dart'; +import 'package:island/core/services/sharing_intent.dart'; +import 'package:island/core/services/update_service.dart'; +import 'package:island/core/widgets/content/network_status_sheet.dart'; +import 'package:island/core/tour/tour.dart'; +import 'package:island/posts/posts_widgets/compose_sheet.dart'; +import 'package:island/core/services/event_bus.dart'; import 'package:snow_fall_animation/snow_fall_animation.dart'; import 'package:tray_manager/tray_manager.dart'; import 'package:window_manager/window_manager.dart'; diff --git a/lib/widgets/attachment_uploader.dart b/lib/shared/widgets/attachment_uploader.dart similarity index 97% rename from lib/widgets/attachment_uploader.dart rename to lib/shared/widgets/attachment_uploader.dart index ace0791e..1eff1fdd 100644 --- a/lib/widgets/attachment_uploader.dart +++ b/lib/shared/widgets/attachment_uploader.dart @@ -3,14 +3,14 @@ import 'dart:typed_data'; import 'package:cross_file/cross_file.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/file_pool.dart'; -import 'package:island/pods/drive/file_pool.dart'; -import 'package:island/widgets/content/attachment_preview.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/post/compose_shared.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/drive/drive_models/file_pool.dart'; +import 'package:island/drive/drive/file_pool.dart'; +import 'package:island/core/widgets/content/attachment_preview.dart'; +import 'package:island/core/widgets/content/sheet.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:gap/gap.dart'; +import 'package:island/posts/posts_widgets/post/compose_shared.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/empty_state.dart b/lib/shared/widgets/empty_state.dart similarity index 100% rename from lib/widgets/empty_state.dart rename to lib/shared/widgets/empty_state.dart diff --git a/lib/widgets/extended_refresh_indicator.dart b/lib/shared/widgets/extended_refresh_indicator.dart similarity index 100% rename from lib/widgets/extended_refresh_indicator.dart rename to lib/shared/widgets/extended_refresh_indicator.dart diff --git a/lib/widgets/sites/info_row.dart b/lib/shared/widgets/info_row.dart similarity index 100% rename from lib/widgets/sites/info_row.dart rename to lib/shared/widgets/info_row.dart diff --git a/lib/widgets/loading_indicator.dart b/lib/shared/widgets/loading_indicator.dart similarity index 100% rename from lib/widgets/loading_indicator.dart rename to lib/shared/widgets/loading_indicator.dart diff --git a/lib/widgets/paging/pagination_list.dart b/lib/shared/widgets/pagination_list.dart similarity index 98% rename from lib/widgets/paging/pagination_list.dart rename to lib/shared/widgets/pagination_list.dart index c4395f87..999a1cb0 100644 --- a/lib/widgets/paging/pagination_list.dart +++ b/lib/shared/widgets/pagination_list.dart @@ -4,9 +4,9 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/misc.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:skeletonizer/skeletonizer.dart'; diff --git a/lib/widgets/response.dart b/lib/shared/widgets/response.dart similarity index 98% rename from lib/widgets/response.dart rename to lib/shared/widgets/response.dart index 7289bd69..c76357dd 100644 --- a/lib/widgets/response.dart +++ b/lib/shared/widgets/response.dart @@ -2,7 +2,7 @@ import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:island/screens/auth/login_modal.dart'; +import 'package:island/auth/login_modal.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/common/responsive_sidebar.dart b/lib/shared/widgets/responsive_sidebar.dart similarity index 90% rename from lib/widgets/common/responsive_sidebar.dart rename to lib/shared/widgets/responsive_sidebar.dart index d6e801a1..0eb3a629 100644 --- a/lib/widgets/common/responsive_sidebar.dart +++ b/lib/shared/widgets/responsive_sidebar.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/services/responsive.dart'; -import 'package:island/widgets/post/article_sidebar_panel.dart'; +import 'package:island/core/services/responsive.dart'; +import 'package:island/posts/posts_widgets/post/article_sidebar_panel.dart'; class ResponsiveSidebar extends HookConsumerWidget { final Widget attachmentsContent; @@ -118,16 +118,16 @@ class ResponsiveSidebar extends HookConsumerWidget { key: scaffoldKey, endDrawer: Drawer( width: sidebarWidth, - child: ArticleSidebarPanelWidget( - attachmentsContent: attachmentsContent, - settingsContent: settingsContent, - onClose: () { - showSidebar.value = false; - Navigator.of(context).pop(); - }, - isWide: false, - width: sidebarWidth, - ), + child: ArticleSidebarPanelWidget( + attachmentsContent: attachmentsContent, + settingsContent: settingsContent, + onClose: () { + showSidebar.value = false; + Navigator.of(context).pop(); + }, + isWide: false, + width: sidebarWidth, + ), ), body: mainContent, ); @@ -174,4 +174,4 @@ class ResponsiveSidebar extends HookConsumerWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/widgets/task_overlay.dart b/lib/shared/widgets/task_overlay.dart similarity index 99% rename from lib/widgets/task_overlay.dart rename to lib/shared/widgets/task_overlay.dart index 5a030571..a6afc3e0 100644 --- a/lib/widgets/task_overlay.dart +++ b/lib/shared/widgets/task_overlay.dart @@ -4,9 +4,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/drive_task.dart'; -import 'package:island/pods/drive/upload_tasks.dart'; -import 'package:island/services/responsive.dart'; +import 'package:island/drive/drive_models/drive_task.dart'; +import 'package:island/drive/drive/upload_tasks.dart'; +import 'package:island/core/services/responsive.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/pods/site_files.dart b/lib/sites/site_files.dart similarity index 97% rename from lib/pods/site_files.dart rename to lib/sites/site_files.dart index bf389cb7..2bcda7ad 100644 --- a/lib/pods/site_files.dart +++ b/lib/sites/site_files.dart @@ -3,8 +3,8 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:http_parser/http_parser.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:island/models/site_file.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/discovery/discovery_models/site_file.dart'; +import 'package:island/core/network.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'site_files.g.dart'; diff --git a/lib/pods/site_files.g.dart b/lib/sites/site_files.g.dart similarity index 100% rename from lib/pods/site_files.g.dart rename to lib/sites/site_files.g.dart diff --git a/lib/pods/site_pages.dart b/lib/sites/site_pages.dart similarity index 96% rename from lib/pods/site_pages.dart rename to lib/sites/site_pages.dart index 0204c910..4ee8f0e1 100644 --- a/lib/pods/site_pages.dart +++ b/lib/sites/site_pages.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/pods/network.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/core/network.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'site_pages.g.dart'; diff --git a/lib/pods/site_pages.g.dart b/lib/sites/site_pages.g.dart similarity index 100% rename from lib/pods/site_pages.g.dart rename to lib/sites/site_pages.g.dart diff --git a/lib/widgets/sites/file_item.dart b/lib/sites/sites_widgets/file_item.dart similarity index 96% rename from lib/widgets/sites/file_item.dart rename to lib/sites/sites_widgets/file_item.dart index 04950774..085f0bdc 100644 --- a/lib/widgets/sites/file_item.dart +++ b/lib/sites/sites_widgets/file_item.dart @@ -6,18 +6,18 @@ import 'package:flutter_highlight/themes/monokai-sublime.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/site_file.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/pods/site_files.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/discovery/discovery_models/site_file.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/sites/site_files.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:path_provider/path_provider.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:photo_view/photo_view.dart'; -import 'package:island/pods/config.dart'; +import 'package:island/core/config.dart'; class FileItem extends HookConsumerWidget { final SnSiteFileEntry file; diff --git a/lib/widgets/sites/file_management_action_section.dart b/lib/sites/sites_widgets/file_management_action_section.dart similarity index 95% rename from lib/widgets/sites/file_management_action_section.dart rename to lib/sites/sites_widgets/file_management_action_section.dart index 161bb0f2..877b6d4a 100644 --- a/lib/widgets/sites/file_management_action_section.dart +++ b/lib/sites/sites_widgets/file_management_action_section.dart @@ -6,10 +6,10 @@ import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:dio/dio.dart'; import 'package:http_parser/http_parser.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/pods/site_files.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/sites/site_files.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/sites/file_management_section.dart b/lib/sites/sites_widgets/file_management_section.dart similarity index 97% rename from lib/widgets/sites/file_management_section.dart rename to lib/sites/sites_widgets/file_management_section.dart index ae395a28..c6351af6 100644 --- a/lib/widgets/sites/file_management_section.dart +++ b/lib/sites/sites_widgets/file_management_section.dart @@ -6,11 +6,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/pods/site_files.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/sites/file_upload_dialog.dart'; -import 'package:island/widgets/sites/file_item.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/sites/site_files.dart'; +import 'package:island/sites/sites_widgets/file_item.dart'; +import 'package:island/sites/sites_widgets/file_upload_dialog.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:path/path.dart' as p; diff --git a/lib/widgets/sites/file_upload_dialog.dart b/lib/sites/sites_widgets/file_upload_dialog.dart similarity index 78% rename from lib/widgets/sites/file_upload_dialog.dart rename to lib/sites/sites_widgets/file_upload_dialog.dart index 6e31d134..abf391ad 100644 --- a/lib/widgets/sites/file_upload_dialog.dart +++ b/lib/sites/sites_widgets/file_upload_dialog.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/pods/site_files.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/sites/site_files.dart'; import 'package:material_symbols_icons/symbols.dart'; class FileUploadDialog extends HookConsumerWidget { @@ -45,24 +45,22 @@ class FileUploadDialog extends HookConsumerWidget { ); // Calculate overall progress - final overallProgress = - progressStates.value.isNotEmpty - ? progressStates.value - .map((e) => e['progress'] as double) - .reduce((a, b) => a + b) / - progressStates.value.length - : 0.0; + final overallProgress = progressStates.value.isNotEmpty + ? progressStates.value + .map((e) => e['progress'] as double) + .reduce((a, b) => a + b) / + progressStates.value.length + : 0.0; - final overallStatus = - progressStates.value.isEmpty - ? 'pending' - : progressStates.value.every((e) => e['status'] == 'completed') - ? 'completed' - : progressStates.value.any((e) => e['status'] == 'error') - ? 'error' - : progressStates.value.any((e) => e['status'] == 'uploading') - ? 'uploading' - : 'pending'; + final overallStatus = progressStates.value.isEmpty + ? 'pending' + : progressStates.value.every((e) => e['status'] == 'completed') + ? 'completed' + : progressStates.value.any((e) => e['status'] == 'error') + ? 'error' + : progressStates.value.any((e) => e['status'] == 'uploading') + ? 'uploading' + : 'pending'; final uploadFile = useCallback(( String basePath, @@ -78,10 +76,9 @@ class FileUploadDialog extends HookConsumerWidget { ); final fileName = relativePaths?[index] ?? file.path.split('/').last; - final uploadPath = - basePath.endsWith('/') - ? '$basePath$fileName' - : '$basePath/$fileName'; + final uploadPath = basePath.endsWith('/') + ? '$basePath$fileName' + : '$basePath/$fileName'; await siteFilesNotifier.uploadFile(file, uploadPath); @@ -174,8 +171,8 @@ class FileUploadDialog extends HookConsumerWidget { } return null; }, - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), ), const Gap(16), Card( @@ -207,23 +204,21 @@ class FileUploadDialog extends HookConsumerWidget { ExpansionTile( title: Text('${selectedFiles.length} files to upload'), initiallyExpanded: selectedFiles.length <= 10, - children: - selectedFiles.map((file) { - final index = selectedFiles.indexOf(file); - final progressState = progressStates.value[index]; - final displayName = - progressState['fileName'] as String; - return ListTile( - leading: _getStatusIcon( - progressState['status'] as String, - ), - title: Text(displayName), - subtitle: Text( - 'Size: ${(file.lengthSync() / 1024).toStringAsFixed(1)} KB', - ), - dense: true, - ); - }).toList(), + children: selectedFiles.map((file) { + final index = selectedFiles.indexOf(file); + final progressState = progressStates.value[index]; + final displayName = progressState['fileName'] as String; + return ListTile( + leading: _getStatusIcon( + progressState['status'] as String, + ), + title: Text(displayName), + subtitle: Text( + 'Size: ${(file.lengthSync() / 1024).toStringAsFixed(1)} KB', + ), + dense: true, + ); + }).toList(), ), ], ), @@ -233,10 +228,9 @@ class FileUploadDialog extends HookConsumerWidget { children: [ Expanded( child: OutlinedButton( - onPressed: - isUploading.value - ? null - : () => Navigator.of(context).pop(), + onPressed: isUploading.value + ? null + : () => Navigator.of(context).pop(), child: const Text('Cancel'), ), ), diff --git a/lib/widgets/sites/page_form.dart b/lib/sites/sites_widgets/page_form.dart similarity index 99% rename from lib/widgets/sites/page_form.dart rename to lib/sites/sites_widgets/page_form.dart index bcd32a97..1b837a9d 100644 --- a/lib/widgets/sites/page_form.dart +++ b/lib/sites/sites_widgets/page_form.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/pods/site_pages.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/sites/site_pages.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/sites/page_item.dart b/lib/sites/sites_widgets/page_item.dart similarity index 93% rename from lib/widgets/sites/page_item.dart rename to lib/sites/sites_widgets/page_item.dart index 7ff4d33b..f38bac4e 100644 --- a/lib/widgets/sites/page_item.dart +++ b/lib/sites/sites_widgets/page_item.dart @@ -2,10 +2,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/pods/site_pages.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/sites/page_form.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/sites/site_pages.dart'; +import 'package:island/sites/sites_widgets/page_form.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:url_launcher/url_launcher_string.dart'; diff --git a/lib/widgets/sites/pages_section.dart b/lib/sites/sites_widgets/pages_section.dart similarity index 79% rename from lib/widgets/sites/pages_section.dart rename to lib/sites/sites_widgets/pages_section.dart index 66caf192..cb31721d 100644 --- a/lib/widgets/sites/pages_section.dart +++ b/lib/sites/sites_widgets/pages_section.dart @@ -2,10 +2,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/pods/site_pages.dart'; -import 'package:island/widgets/sites/page_form.dart'; -import 'package:island/widgets/sites/page_item.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/sites/site_pages.dart'; +import 'package:island/sites/sites_widgets/page_form.dart'; +import 'package:island/sites/sites_widgets/page_item.dart'; import 'package:material_symbols_icons/symbols.dart'; class PagesSection extends HookConsumerWidget { @@ -42,8 +42,8 @@ class PagesSection extends HookConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => PageForm(site: site, pubName: pubName), + builder: (context) => + PageForm(site: site, pubName: pubName), ).then((_) { // Refresh pages after creation ref.invalidate(sitePagesProvider(pubName, site.slug)); @@ -98,22 +98,19 @@ class PagesSection extends HookConsumerWidget { ); }, loading: () => const Center(child: CircularProgressIndicator()), - error: - (error, stack) => Center( - child: Column( - children: [ - Text('failedToLoadPages'.tr()), - const Gap(8), - ElevatedButton( - onPressed: - () => ref.invalidate( - sitePagesProvider(pubName, site.slug), - ), - child: Text('retry'.tr()), - ), - ], + error: (error, stack) => Center( + child: Column( + children: [ + Text('failedToLoadPages'.tr()), + const Gap(8), + ElevatedButton( + onPressed: () => + ref.invalidate(sitePagesProvider(pubName, site.slug)), + child: Text('retry'.tr()), ), - ), + ], + ), + ), ), ], ), diff --git a/lib/widgets/sites/site_action_menu.dart b/lib/sites/sites_widgets/site_action_menu.dart similarity index 89% rename from lib/widgets/sites/site_action_menu.dart rename to lib/sites/sites_widgets/site_action_menu.dart index 8e74a530..a99fe5cf 100644 --- a/lib/widgets/sites/site_action_menu.dart +++ b/lib/sites/sites_widgets/site_action_menu.dart @@ -2,11 +2,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/creators/sites/site_detail.dart'; -import 'package:island/screens/creators/sites/site_edit.dart'; -import 'package:island/widgets/alert.dart'; +import 'package:island/creators/creators/sites/site_detail.dart'; +import 'package:island/creators/creators/sites/site_edit.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/widgets/sites/site_detail_content.dart b/lib/sites/sites_widgets/site_detail_content.dart similarity index 63% rename from lib/widgets/sites/site_detail_content.dart rename to lib/sites/sites_widgets/site_detail_content.dart index d7bbb5dd..5909922d 100644 --- a/lib/widgets/sites/site_detail_content.dart +++ b/lib/sites/sites_widgets/site_detail_content.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/widgets/sites/file_management_section.dart'; -import 'package:island/widgets/sites/file_management_action_section.dart'; -import 'package:island/widgets/sites/site_info_card.dart'; -import 'package:island/widgets/sites/pages_section.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; -import 'package:island/screens/creators/sites/site_detail.dart'; +import 'package:island/creators/creators/sites/site_detail.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; +import 'package:island/sites/sites_widgets/file_management_action_section.dart'; +import 'package:island/sites/sites_widgets/file_management_section.dart'; +import 'package:island/sites/sites_widgets/pages_section.dart'; +import 'package:island/sites/sites_widgets/site_info_card.dart'; class SiteDetailContent extends HookConsumerWidget { final SnPublicationSite site; @@ -22,9 +22,8 @@ class SiteDetailContent extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return ExtendedRefreshIndicator( - onRefresh: - () async => - ref.invalidate(publicationSiteDetailProvider(pubName, site.slug)), + onRefresh: () async => + ref.invalidate(publicationSiteDetailProvider(pubName, site.slug)), child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( diff --git a/lib/widgets/sites/site_info_card.dart b/lib/sites/sites_widgets/site_info_card.dart similarity index 88% rename from lib/widgets/sites/site_info_card.dart rename to lib/sites/sites_widgets/site_info_card.dart index e65b3260..c106601f 100644 --- a/lib/widgets/sites/site_info_card.dart +++ b/lib/sites/sites_widgets/site_info_card.dart @@ -1,9 +1,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:island/models/publication_site.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/sites/info_row.dart'; +import 'package:island/creators/publication_site.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/shared/widgets/info_row.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -51,10 +51,9 @@ class SiteInfoCard extends StatelessWidget { const Gap(8), InfoRow( label: 'siteMode'.tr(), - value: - site.mode == 0 - ? 'siteModeFullyManaged'.tr() - : 'siteModeSelfManaged'.tr(), + value: site.mode == 0 + ? 'siteModeFullyManaged'.tr() + : 'siteModeSelfManaged'.tr(), icon: Symbols.settings, ), if (site.description != null && site.description!.isNotEmpty) ...[ diff --git a/lib/screens/stickers/pack_detail.dart b/lib/stickers/stickers/pack_detail.dart similarity index 96% rename from lib/screens/stickers/pack_detail.dart rename to lib/stickers/stickers/pack_detail.dart index bee5055a..57c73891 100644 --- a/lib/screens/stickers/pack_detail.dart +++ b/lib/stickers/stickers/pack_detail.dart @@ -4,12 +4,12 @@ import 'package:flutter/services.dart'; import 'package:gap/gap.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/sticker.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/creators/stickers/stickers.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/creators/creators/stickers/stickers.dart'; +import 'package:island/stickers/stickers_models/sticker.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/lib/screens/stickers/pack_detail.g.dart b/lib/stickers/stickers/pack_detail.g.dart similarity index 100% rename from lib/screens/stickers/pack_detail.g.dart rename to lib/stickers/stickers/pack_detail.g.dart diff --git a/lib/screens/stickers/sticker_marketplace.dart b/lib/stickers/stickers/sticker_marketplace.dart similarity index 97% rename from lib/screens/stickers/sticker_marketplace.dart rename to lib/stickers/stickers/sticker_marketplace.dart index 09a4fa76..cfe96a96 100644 --- a/lib/screens/stickers/sticker_marketplace.dart +++ b/lib/stickers/stickers/sticker_marketplace.dart @@ -7,12 +7,12 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:go_router/go_router.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/sticker.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/stickers/stickers_models/sticker.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'dart:async'; diff --git a/lib/screens/stickers/sticker_marketplace.freezed.dart b/lib/stickers/stickers/sticker_marketplace.freezed.dart similarity index 100% rename from lib/screens/stickers/sticker_marketplace.freezed.dart rename to lib/stickers/stickers/sticker_marketplace.freezed.dart diff --git a/lib/models/sticker.dart b/lib/stickers/stickers_models/sticker.dart similarity index 90% rename from lib/models/sticker.dart rename to lib/stickers/stickers_models/sticker.dart index 29ab6742..070f7428 100644 --- a/lib/models/sticker.dart +++ b/lib/stickers/stickers_models/sticker.dart @@ -1,6 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/file.dart'; -import 'package:island/models/publisher.dart'; +import 'package:island/drive/drive_models/file.dart'; +import 'package:island/posts/posts_models/publisher.dart'; part 'sticker.freezed.dart'; part 'sticker.g.dart'; diff --git a/lib/models/sticker.freezed.dart b/lib/stickers/stickers_models/sticker.freezed.dart similarity index 100% rename from lib/models/sticker.freezed.dart rename to lib/stickers/stickers_models/sticker.freezed.dart diff --git a/lib/models/sticker.g.dart b/lib/stickers/stickers_models/sticker.g.dart similarity index 100% rename from lib/models/sticker.g.dart rename to lib/stickers/stickers_models/sticker.g.dart diff --git a/lib/widgets/stickers/sticker_picker.dart b/lib/stickers/stickers_widgets/stickers/sticker_picker.dart similarity index 98% rename from lib/widgets/stickers/sticker_picker.dart rename to lib/stickers/stickers_widgets/stickers/sticker_picker.dart index 5b8616ca..7fd08d89 100644 --- a/lib/widgets/stickers/sticker_picker.dart +++ b/lib/stickers/stickers_widgets/stickers/sticker_picker.dart @@ -5,14 +5,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/sticker.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/stickers/stickers_models/sticker.dart'; +import 'package:island/core/network.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:flutter_popup_card/flutter_popup_card.dart'; -import 'package:island/widgets/extended_refresh_indicator.dart'; +import 'package:island/shared/widgets/extended_refresh_indicator.dart'; part 'sticker_picker.g.dart'; diff --git a/lib/widgets/stickers/sticker_picker.g.dart b/lib/stickers/stickers_widgets/stickers/sticker_picker.g.dart similarity index 100% rename from lib/widgets/stickers/sticker_picker.g.dart rename to lib/stickers/stickers_widgets/stickers/sticker_picker.g.dart diff --git a/lib/models/thought.dart b/lib/thought/thought.dart similarity index 99% rename from lib/models/thought.dart rename to lib/thought/thought.dart index e34330d7..cbf74a2c 100644 --- a/lib/models/thought.dart +++ b/lib/thought/thought.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/file.dart'; +import 'package:island/drive/drive_models/file.dart'; part 'thought.freezed.dart'; part 'thought.g.dart'; diff --git a/lib/models/thought.freezed.dart b/lib/thought/thought.freezed.dart similarity index 100% rename from lib/models/thought.freezed.dart rename to lib/thought/thought.freezed.dart diff --git a/lib/models/thought.g.dart b/lib/thought/thought.g.dart similarity index 100% rename from lib/models/thought.g.dart rename to lib/thought/thought.g.dart diff --git a/lib/screens/thought/think.dart b/lib/thought/thought/think.dart similarity index 51% rename from lib/screens/thought/think.dart rename to lib/thought/thought/think.dart index a1324148..bd6a0fdc 100644 --- a/lib/screens/thought/think.dart +++ b/lib/thought/thought/think.dart @@ -2,15 +2,15 @@ import "package:easy_localization/easy_localization.dart"; import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:gap/gap.dart"; +import "package:island/thought/thought_widgets/thought/thought_sequence_list.dart"; +import "package:island/thought/thought_widgets/thought/thought_shared.dart"; import "package:riverpod_annotation/riverpod_annotation.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:island/models/thought.dart"; -import "package:island/pods/network.dart"; -import "package:island/widgets/alert.dart"; -import "package:island/widgets/app_scaffold.dart"; -import "package:island/widgets/response.dart"; -import "package:island/widgets/thought/thought_sequence_list.dart"; -import "package:island/widgets/thought/thought_shared.dart"; +import "package:island/thought/thought.dart"; +import "package:island/core/network.dart"; +import "package:island/shared/widgets/alert.dart"; +import "package:island/shared/widgets/app_scaffold.dart"; +import "package:island/shared/widgets/response.dart"; import "package:material_symbols_icons/material_symbols_icons.dart"; part 'think.g.dart'; @@ -49,10 +49,9 @@ class ThoughtScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final selectedSequenceId = useState(null); - final thoughts = - selectedSequenceId.value != null - ? ref.watch(thoughtSequenceProvider(selectedSequenceId.value!)) - : const AsyncValue>.data([]); + final thoughts = selectedSequenceId.value != null + ? ref.watch(thoughtSequenceProvider(selectedSequenceId.value!)) + : const AsyncValue>.data([]); // Extract sequence ID from loaded thoughts for the chat interface final sequenceIdFromThoughts = thoughts.maybeWhen( @@ -69,9 +68,9 @@ class ThoughtScreen extends HookConsumerWidget { final initialThoughts = thoughts.value; final initialTopic = (initialThoughts?.isNotEmpty ?? false) && - initialThoughts!.first.sequence?.topic != null - ? initialThoughts.first.sequence!.topic - : 'aiThought'.tr(); + initialThoughts!.first.sequence?.topic != null + ? initialThoughts.first.sequence!.topic + : 'aiThought'.tr(); final statusAsync = ref.watch(thoughtAvailableStausProvider); @@ -87,12 +86,11 @@ class ThoughtScreen extends HookConsumerWidget { // Show sequence selector showModalBottomSheet( context: context, - builder: - (context) => ThoughtSequenceSelector( - onSequenceSelected: (sequenceId) { - selectedSequenceId.value = sequenceId; - }, - ), + builder: (context) => ThoughtSequenceSelector( + onSequenceSelected: (sequenceId) { + selectedSequenceId.value = sequenceId; + }, + ), ); }, ), @@ -119,73 +117,60 @@ class ThoughtScreen extends HookConsumerWidget { ); final thoughtsBody = thoughts.when( - data: - (thoughtList) => ThoughtChatInterface( - initialThoughts: thoughtList, - initialSequenceId: sequenceIdFromThoughts, - initialTopic: initialTopic, - isDisabled: !status, - ), + data: (thoughtList) => ThoughtChatInterface( + initialThoughts: thoughtList, + initialSequenceId: sequenceIdFromThoughts, + initialTopic: initialTopic, + isDisabled: !status, + ), loading: () => const Center(child: CircularProgressIndicator()), - error: - (error, _) => ResponseErrorWidget( - error: error, - onRetry: - () => - selectedSequenceId.value != null - ? ref.invalidate( - thoughtSequenceProvider( - selectedSequenceId.value!, - ), - ) - : null, - ), + error: (error, _) => ResponseErrorWidget( + error: error, + onRetry: () => selectedSequenceId.value != null + ? ref.invalidate( + thoughtSequenceProvider(selectedSequenceId.value!), + ) + : null, + ), ); return status ? thoughtsBody : Column( - children: [ - MaterialBanner( - leading: const Icon(Symbols.error), - content: const Text( - 'You have unpaid orders. Please settle your payment to continue using the service.', - style: TextStyle(fontWeight: FontWeight.bold), - ), - actions: [ - TextButton( - onPressed: () { - retry(); - }, - child: Text('retry'.tr()), + children: [ + MaterialBanner( + leading: const Icon(Symbols.error), + content: const Text( + 'You have unpaid orders. Please settle your payment to continue using the service.', + style: TextStyle(fontWeight: FontWeight.bold), ), - ], - ), - Expanded(child: thoughtsBody), - ], - ); + actions: [ + TextButton( + onPressed: () { + retry(); + }, + child: Text('retry'.tr()), + ), + ], + ), + Expanded(child: thoughtsBody), + ], + ); }, - orElse: - () => thoughts.when( - data: - (thoughtList) => ThoughtChatInterface( - initialThoughts: thoughtList, - initialTopic: initialTopic, - ), - loading: () => const Center(child: CircularProgressIndicator()), - error: - (error, _) => ResponseErrorWidget( - error: error, - onRetry: - () => - selectedSequenceId.value != null - ? ref.invalidate( - thoughtSequenceProvider( - selectedSequenceId.value!, - ), - ) - : null, - ), - ), + orElse: () => thoughts.when( + data: (thoughtList) => ThoughtChatInterface( + initialThoughts: thoughtList, + initialTopic: initialTopic, + ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, _) => ResponseErrorWidget( + error: error, + onRetry: () => selectedSequenceId.value != null + ? ref.invalidate( + thoughtSequenceProvider(selectedSequenceId.value!), + ) + : null, + ), + ), ), ); } diff --git a/lib/screens/thought/think.g.dart b/lib/thought/thought/think.g.dart similarity index 100% rename from lib/screens/thought/think.g.dart rename to lib/thought/thought/think.g.dart diff --git a/lib/screens/thought/think_sheet.dart b/lib/thought/thought/think_sheet.dart similarity index 92% rename from lib/screens/thought/think_sheet.dart rename to lib/thought/thought/think_sheet.dart index 1dab102f..749645f6 100644 --- a/lib/screens/thought/think_sheet.dart +++ b/lib/thought/thought/think_sheet.dart @@ -2,11 +2,11 @@ import "package:easy_localization/easy_localization.dart"; import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:island/pods/network.dart"; -import "package:island/screens/thought/think.dart"; -import "package:island/widgets/alert.dart"; -import "package:island/widgets/content/sheet.dart"; -import "package:island/widgets/thought/thought_shared.dart"; +import "package:island/core/network.dart"; +import "package:island/shared/widgets/alert.dart"; +import "package:island/core/widgets/content/sheet.dart"; +import "package:island/thought/thought/think.dart"; +import "package:island/thought/thought_widgets/thought/thought_shared.dart"; import "package:material_symbols_icons/material_symbols_icons.dart"; class ThoughtSheet extends HookConsumerWidget { diff --git a/lib/widgets/thought/function_calls_section.dart b/lib/thought/thought_widgets/thought/function_calls_section.dart similarity index 100% rename from lib/widgets/thought/function_calls_section.dart rename to lib/thought/thought_widgets/thought/function_calls_section.dart diff --git a/lib/widgets/thought/proposals_section.dart b/lib/thought/thought_widgets/thought/proposals_section.dart similarity index 100% rename from lib/widgets/thought/proposals_section.dart rename to lib/thought/thought_widgets/thought/proposals_section.dart diff --git a/lib/widgets/thought/reasoning_section.dart b/lib/thought/thought_widgets/thought/reasoning_section.dart similarity index 100% rename from lib/widgets/thought/reasoning_section.dart rename to lib/thought/thought_widgets/thought/reasoning_section.dart diff --git a/lib/widgets/thought/thought_content.dart b/lib/thought/thought_widgets/thought/thought_content.dart similarity index 91% rename from lib/widgets/thought/thought_content.dart rename to lib/thought/thought_widgets/thought/thought_content.dart index f0d9a6b5..eb8d7cd5 100644 --- a/lib/widgets/thought/thought_content.dart +++ b/lib/thought/thought_widgets/thought/thought_content.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:island/models/thought.dart'; -import 'package:island/widgets/content/markdown.dart'; -import 'package:island/widgets/thought/thought_proposal.dart'; +import 'package:island/thought/thought.dart'; +import 'package:island/core/widgets/content/markdown.dart'; +import 'package:island/thought/thought_widgets/thought/thought_proposal.dart'; class ThoughtContent extends StatelessWidget { const ThoughtContent({ diff --git a/lib/widgets/thought/thought_header.dart b/lib/thought/thought_widgets/thought/thought_header.dart similarity index 100% rename from lib/widgets/thought/thought_header.dart rename to lib/thought/thought_widgets/thought/thought_header.dart diff --git a/lib/widgets/thought/thought_proposal.dart b/lib/thought/thought_widgets/thought/thought_proposal.dart similarity index 100% rename from lib/widgets/thought/thought_proposal.dart rename to lib/thought/thought_widgets/thought/thought_proposal.dart diff --git a/lib/widgets/thought/thought_sequence_list.dart b/lib/thought/thought_widgets/thought/thought_sequence_list.dart similarity index 85% rename from lib/widgets/thought/thought_sequence_list.dart rename to lib/thought/thought_widgets/thought/thought_sequence_list.dart index 0b76c16a..8cd0d6bc 100644 --- a/lib/widgets/thought/thought_sequence_list.dart +++ b/lib/thought/thought_widgets/thought/thought_sequence_list.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/thought.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/services/time.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/thought/thought.dart'; +import 'package:island/core/network.dart'; +import 'package:island/core/services/time.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; final thoughtSequenceListNotifierProvider = AsyncNotifierProvider.autoDispose( ThoughtSequenceListNotifier.new, diff --git a/lib/widgets/thought/thought_shared.dart b/lib/thought/thought_widgets/thought/thought_shared.dart similarity index 97% rename from lib/widgets/thought/thought_shared.dart rename to lib/thought/thought_widgets/thought/thought_shared.dart index 8d01e9e3..01993e5e 100644 --- a/lib/widgets/thought/thought_shared.dart +++ b/lib/thought/thought_widgets/thought/thought_shared.dart @@ -8,20 +8,20 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/thought.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/screens/thought/think.dart'; -import 'package:island/screens/posts/compose.dart'; +import 'package:island/thought/thought.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/posts/compose.dart'; import 'package:island/talker.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/post/compose_sheet.dart'; -import 'package:island/widgets/thought/function_calls_section.dart'; -import 'package:island/widgets/thought/proposals_section.dart'; -import 'package:island/widgets/thought/reasoning_section.dart'; -import 'package:island/widgets/thought/thought_content.dart'; -import 'package:island/widgets/thought/thought_header.dart'; -import 'package:island/widgets/thought/token_info.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/posts/posts_widgets/compose_sheet.dart'; +import 'package:island/thought/thought/think.dart'; +import 'package:island/thought/thought_widgets/thought/function_calls_section.dart'; +import 'package:island/thought/thought_widgets/thought/proposals_section.dart'; +import 'package:island/thought/thought_widgets/thought/reasoning_section.dart'; +import 'package:island/thought/thought_widgets/thought/thought_content.dart'; +import 'package:island/thought/thought_widgets/thought/thought_header.dart'; +import 'package:island/thought/thought_widgets/thought/token_info.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; diff --git a/lib/widgets/thought/token_info.dart b/lib/thought/thought_widgets/thought/token_info.dart similarity index 96% rename from lib/widgets/thought/token_info.dart rename to lib/thought/thought_widgets/thought/token_info.dart index a4e8f354..11d30936 100644 --- a/lib/widgets/thought/token_info.dart +++ b/lib/thought/thought_widgets/thought/token_info.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; -import 'package:island/models/thought.dart'; +import 'package:island/thought/thought.dart'; class TokenInfo extends StatelessWidget { const TokenInfo({super.key, required this.thought}); diff --git a/lib/screens/wallet.dart b/lib/wallet/wallet.dart similarity index 98% rename from lib/screens/wallet.dart rename to lib/wallet/wallet.dart index fe6281cf..ae0afb75 100644 --- a/lib/screens/wallet.dart +++ b/lib/wallet/wallet.dart @@ -5,22 +5,22 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/account.dart'; -import 'package:island/models/wallet.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/screens/lottery.dart'; -import 'package:island/widgets/account/account_pfc.dart'; -import 'package:island/widgets/account/account_picker.dart'; -import 'package:island/widgets/app_scaffold.dart'; -import 'package:island/widgets/content/cloud_files.dart'; -import 'package:island/widgets/alert.dart'; -import 'package:island/widgets/content/sheet.dart'; -import 'package:island/widgets/payment/payment_overlay.dart'; -import 'package:island/widgets/response.dart'; +import 'package:island/accounts/accounts_models/account.dart'; +import 'package:island/accounts/accounts_widgets/account/account_pfc.dart'; +import 'package:island/accounts/accounts_widgets/account/account_picker.dart'; +import 'package:island/lotteries/lottery.dart'; +import 'package:island/pagination/pagination.dart'; +import 'package:island/wallet/wallet_models/wallet.dart'; +import 'package:island/core/network.dart'; +import 'package:island/shared/widgets/app_scaffold.dart'; +import 'package:island/drive/drive_widgets/cloud_files.dart'; +import 'package:island/shared/widgets/alert.dart'; +import 'package:island/core/widgets/content/sheet.dart'; +import 'package:island/core/widgets/payment/payment_overlay.dart'; +import 'package:island/shared/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:island/pods/paging.dart'; -import 'package:island/widgets/paging/pagination_list.dart'; +import 'package:island/shared/widgets/pagination_list.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; @@ -1135,7 +1135,7 @@ class TransactionDetailSheet extends StatelessWidget { ), ), const Gap(4), - AccountPfcGestureDetector( + AccountPfcRegion( uname: transaction.payerWallet?.account?.name, child: Row( spacing: 8, @@ -1165,7 +1165,7 @@ class TransactionDetailSheet extends StatelessWidget { ), ), const Gap(4), - AccountPfcGestureDetector( + AccountPfcRegion( uname: transaction.payeeWallet?.account?.name, child: Row( spacing: 8, diff --git a/lib/screens/wallet.g.dart b/lib/wallet/wallet.g.dart similarity index 94% rename from lib/screens/wallet.g.dart rename to lib/wallet/wallet.g.dart index 8b68f1de..233de0e8 100644 --- a/lib/screens/wallet.g.dart +++ b/lib/wallet/wallet.g.dart @@ -45,7 +45,7 @@ final class WalletCurrentProvider } } -String _$walletCurrentHash() => r'bdc7cb27ce2286b561a03522085cc4efc884faad'; +String _$walletCurrentHash() => r'6a654d34aa7e3002edf97749e386b0bd3db641bb'; @ProviderFor(walletStats) final walletStatsProvider = WalletStatsProvider._(); @@ -84,7 +84,7 @@ final class WalletStatsProvider } } -String _$walletStatsHash() => r'2243011937b377a66cdf44cae144021cee69e82f'; +String _$walletStatsHash() => r'fa265e0fa6e59e1687f6e00c2f9579aa619fcefd'; @ProviderFor(walletFund) final walletFundProvider = WalletFundFamily._(); @@ -141,7 +141,7 @@ final class WalletFundProvider } } -String _$walletFundHash() => r'459efdee5e2775eedaa4312e0d317c218fa7e1fa'; +String _$walletFundHash() => r'adbbc98e0054ee3687c93b7905dabaa84bfeaf1f'; final class WalletFundFamily extends $Family with $FunctionalFamilyOverride, String> { diff --git a/lib/models/wallet.dart b/lib/wallet/wallet_models/wallet.dart similarity index 99% rename from lib/models/wallet.dart rename to lib/wallet/wallet_models/wallet.dart index 4cfc0e70..c8757736 100644 --- a/lib/models/wallet.dart +++ b/lib/wallet/wallet_models/wallet.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:island/models/account.dart'; +import 'package:island/accounts/accounts_models/account.dart'; part 'wallet.freezed.dart'; part 'wallet.g.dart'; diff --git a/lib/models/wallet.freezed.dart b/lib/wallet/wallet_models/wallet.freezed.dart similarity index 100% rename from lib/models/wallet.freezed.dart rename to lib/wallet/wallet_models/wallet.freezed.dart diff --git a/lib/models/wallet.g.dart b/lib/wallet/wallet_models/wallet.g.dart similarity index 100% rename from lib/models/wallet.g.dart rename to lib/wallet/wallet_models/wallet.g.dart diff --git a/lib/widgets/wallet/fund_envelope.dart b/lib/wallet/wallet_widgets/wallet/fund_envelope.dart similarity index 99% rename from lib/widgets/wallet/fund_envelope.dart rename to lib/wallet/wallet_widgets/wallet/fund_envelope.dart index dcab3acd..c9e150b9 100644 --- a/lib/widgets/wallet/fund_envelope.dart +++ b/lib/wallet/wallet_widgets/wallet/fund_envelope.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/wallet.dart'; -import 'package:island/pods/network.dart'; -import 'package:island/pods/userinfo.dart'; -import 'package:island/widgets/alert.dart'; +import 'package:island/wallet/wallet_models/wallet.dart'; +import 'package:island/core/network.dart'; +import 'package:island/accounts/accounts_pod.dart'; +import 'package:island/shared/widgets/alert.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:easy_localization/easy_localization.dart'; diff --git a/lib/widgets/wallet/fund_envelope.g.dart b/lib/wallet/wallet_widgets/wallet/fund_envelope.g.dart similarity index 96% rename from lib/widgets/wallet/fund_envelope.g.dart rename to lib/wallet/wallet_widgets/wallet/fund_envelope.g.dart index 324d101c..48374d24 100644 --- a/lib/widgets/wallet/fund_envelope.g.dart +++ b/lib/wallet/wallet_widgets/wallet/fund_envelope.g.dart @@ -64,7 +64,7 @@ final class WalletFundProvider } } -String _$walletFundHash() => r'521fa280708e71266f8164268ba11f135f4ba810'; +String _$walletFundHash() => r'566b74f1338de8825c19edbc987f942e05f65f49'; final class WalletFundFamily extends $Family with $FunctionalFamilyOverride, String> { diff --git a/lib/widgets/account/activity_presence.dart b/lib/widgets/account/activity_presence.dart deleted file mode 100644 index c5e3ac3f..00000000 --- a/lib/widgets/account/activity_presence.dart +++ /dev/null @@ -1,644 +0,0 @@ -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:dio/dio.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/activity.dart'; -import 'package:island/pods/activity/activity_rpc.dart'; -import 'package:island/widgets/content/image.dart'; -import 'package:material_symbols_icons/symbols.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:styled_widget/styled_widget.dart'; -import 'package:url_launcher/url_launcher_string.dart'; - -part 'activity_presence.g.dart'; - -@riverpod -Future?> discordAssets( - Ref ref, - SnPresenceActivity activity, -) async { - final hasDiscordSmall = - activity.smallImage != null && - activity.smallImage!.startsWith('discord:'); - final hasDiscordLarge = - activity.largeImage != null && - activity.largeImage!.startsWith('discord:'); - - if (hasDiscordSmall || hasDiscordLarge) { - final dio = Dio(); - final response = await dio.get( - 'https://discordapp.com/api/oauth2/applications/${activity.manualId}/assets', - ); - final data = response.data as List; - return { - for (final item in data) item['name'] as String: item['id'] as String, - }; - } - - return null; -} - -@riverpod -Future discordAssetsUrl( - Ref ref, - SnPresenceActivity activity, - String key, -) async { - final assets = await ref.watch(discordAssetsProvider(activity).future); - if (assets != null && assets.containsKey(key)) { - final assetId = assets[key]!; - return 'https://cdn.discordapp.com/app-assets/${activity.manualId}/$assetId.png'; - } - return null; -} - -const kPresenceActivityTypes = [ - 'unknown', - 'presenceTypeGaming', - 'presenceTypeMusic', - 'presenceTypeWorkout', -]; - -const kPresenceActivityIcons = [ - Symbols.question_mark_rounded, - Symbols.play_arrow_rounded, - Symbols.music_note_rounded, - Symbols.running_with_errors, -]; - -class ActivityPresenceWidget extends StatefulWidget { - final String uname; - final bool isCompact; - final EdgeInsets compactPadding; - - const ActivityPresenceWidget({ - super.key, - required this.uname, - this.isCompact = false, - this.compactPadding = EdgeInsets.zero, - }); - - @override - State createState() => _ActivityPresenceWidgetState(); -} - -class _ActivityPresenceWidgetState extends State - with TickerProviderStateMixin { - late AnimationController _progressController; - late Animation _progressAnimation; - double _startProgress = 0.0; - double _endProgress = 0.0; - - @override - void initState() { - super.initState(); - _progressController = AnimationController( - vsync: this, - duration: const Duration(seconds: 1), - ); - _progressAnimation = Tween( - begin: 0.0, - end: 0.0, - ).animate(_progressController); - } - - @override - void dispose() { - _progressController.dispose(); - super.dispose(); - } - - List _buildImages(WidgetRef ref, SnPresenceActivity activity) { - final List images = []; - - if (activity.largeImage != null) { - if (activity.largeImage!.startsWith('discord:')) { - final key = activity.largeImage!.substring('discord:'.length); - final urlAsync = ref.watch(discordAssetsUrlProvider(activity, key)); - images.add( - urlAsync.when( - data: - (url) => - url != null - ? ClipRRect( - borderRadius: BorderRadius.circular(8), - child: CachedNetworkImage( - imageUrl: url, - width: 64, - height: 64, - ), - ) - : const SizedBox.shrink(), - loading: - () => const SizedBox( - width: 64, - height: 64, - child: CircularProgressIndicator(strokeWidth: 2), - ), - error: (error, stack) => const SizedBox.shrink(), - ), - ); - } else { - images.add( - ClipRRect( - borderRadius: BorderRadius.circular(8), - child: UniversalImage( - uri: activity.largeImage!, - width: 64, - height: 64, - ), - ), - ); - } - } - - if (activity.smallImage != null) { - if (activity.smallImage!.startsWith('discord:')) { - final key = activity.smallImage!.substring('discord:'.length); - final urlAsync = ref.watch(discordAssetsUrlProvider(activity, key)); - images.add( - urlAsync.when( - data: - (url) => - url != null - ? ClipRRect( - borderRadius: BorderRadius.circular(8), - child: CachedNetworkImage( - imageUrl: url, - width: 32, - height: 32, - ), - ) - : const SizedBox.shrink(), - loading: - () => const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ), - error: (error, stack) => const SizedBox.shrink(), - ), - ); - } else { - images.add( - ClipRRect( - borderRadius: BorderRadius.circular(8), - child: UniversalImage( - uri: activity.smallImage!, - width: 32, - height: 32, - ), - ), - ); - } - } - - return images; - } - - @override - Widget build(BuildContext context) { - return Consumer( - builder: (BuildContext context, WidgetRef ref, Widget? child) { - final activitiesAsync = ref.watch( - presenceActivitiesProvider(widget.uname), - ); - - if (widget.isCompact) { - return activitiesAsync.when( - data: (activities) { - if (activities.isEmpty) return const SizedBox.shrink(); - final activity = activities.first; - return Padding( - padding: widget.compactPadding, - child: Row( - spacing: 8, - children: [ - if (activity.largeImage != null) - activity.largeImage!.startsWith('discord:') - ? ref - .watch( - discordAssetsUrlProvider( - activity, - activity.largeImage!.substring( - 'discord:'.length, - ), - ), - ) - .when( - data: - (url) => - url != null - ? ClipRRect( - borderRadius: - BorderRadius.circular(4), - child: CachedNetworkImage( - imageUrl: url, - width: 32, - height: 32, - ), - ) - : const SizedBox.shrink(), - loading: - () => const SizedBox( - width: 32, - height: 32, - child: CircularProgressIndicator( - strokeWidth: 1, - ), - ), - error: - (error, stack) => const SizedBox.shrink(), - ) - : ClipRRect( - borderRadius: BorderRadius.circular(4), - child: UniversalImage( - uri: activity.largeImage!, - width: 32, - height: 32, - ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - (activity.title?.isEmpty ?? true) - ? 'unknown'.tr() - : activity.title!, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ).fontSize(13), - Row( - children: [ - Text( - kPresenceActivityTypes[activity.type], - ).tr().fontSize(11), - Icon( - kPresenceActivityIcons[activity.type], - size: 15, - fill: 1, - ), - ], - ), - ], - ), - ), - StreamBuilder( - stream: Stream.periodic(const Duration(seconds: 1)), - builder: (context, snapshot) { - final now = DateTime.now(); - - if (activity.manualId == 'spotify' && - activity.meta != null) { - final meta = activity.meta as Map; - final progressMs = meta['progress_ms'] as int? ?? 0; - final durationMs = - meta['track_duration_ms'] as int? ?? 1; - final elapsed = - now.difference(activity.createdAt).inMilliseconds; - final currentProgressMs = - (progressMs + elapsed) % durationMs; - final progressValue = currentProgressMs / durationMs; - if (progressValue != _endProgress) { - _startProgress = _endProgress; - _endProgress = progressValue; - _progressAnimation = Tween( - begin: _startProgress, - end: _endProgress, - ).animate(_progressController); - _progressController.forward(from: 0.0); - } - return AnimatedBuilder( - animation: _progressAnimation, - builder: (context, child) { - final animatedValue = _progressAnimation.value; - final animatedProgressMs = - (animatedValue * durationMs).toInt(); - final currentMin = animatedProgressMs ~/ 60000; - final currentSec = - (animatedProgressMs % 60000) ~/ 1000; - return Column( - crossAxisAlignment: CrossAxisAlignment.end, - spacing: 2, - children: [ - Text( - '${currentMin.toString().padLeft(2, '0')}:${currentSec.toString().padLeft(2, '0')}', - style: TextStyle( - fontSize: 10, - color: Colors.green, - ), - ), - SizedBox( - width: 120, - child: LinearProgressIndicator( - value: animatedValue, - backgroundColor: Colors.grey.shade300, - stopIndicatorColor: Colors.green, - trackGap: 0, - valueColor: AlwaysStoppedAnimation( - Colors.green, - ), - ), - ).padding(top: 2), - ], - ); - }, - ); - } else { - final duration = now.difference(activity.createdAt); - final hours = duration.inHours.toString().padLeft( - 2, - '0', - ); - final minutes = (duration.inMinutes % 60) - .toString() - .padLeft(2, '0'); - final seconds = (duration.inSeconds % 60) - .toString() - .padLeft(2, '0'); - return Text( - '$hours:$minutes:$seconds', - ).textColor(Colors.green).fontSize(12); - } - }, - ), - ], - ), - ); - }, - loading: () => const SizedBox.shrink(), - error: (error, stack) => const SizedBox.shrink(), - ); - } - - return activitiesAsync.when( - data: - (activities) => Card( - margin: EdgeInsets.zero, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 8, - children: [ - Text( - 'activities', - ).tr().bold().padding(horizontal: 16, vertical: 4), - if (activities.isEmpty) - Row( - spacing: 4, - children: [ - const Icon(Symbols.inbox, size: 16), - Text('dataEmpty').tr().fontSize(13), - ], - ).opacity(0.75).padding(horizontal: 16, bottom: 8), - ...activities.map((activity) { - final images = _buildImages(ref, activity); - - return Stack( - children: [ - Card( - elevation: 0, - shape: RoundedRectangleBorder( - side: BorderSide( - color: Colors.grey.shade300, - width: 1, - ), - borderRadius: BorderRadius.circular(8), - ), - margin: EdgeInsets.zero, - child: ListTile( - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (images.isNotEmpty) - Row( - crossAxisAlignment: - CrossAxisAlignment.end, - spacing: 8, - children: images, - ).padding(vertical: 4), - Row( - spacing: 2, - children: [ - Flexible( - child: Text( - (activity.title?.isEmpty ?? true) - ? 'unknown'.tr() - : activity.title!, - ), - ), - if (activity.titleUrl != null && - activity.titleUrl!.isNotEmpty) - IconButton( - visualDensity: const VisualDensity( - vertical: -4, - ), - onPressed: () { - launchUrlString(activity.titleUrl!); - }, - icon: const Icon( - Symbols.launch_rounded, - ), - iconSize: 16, - padding: EdgeInsets.all(4), - constraints: const BoxConstraints( - maxWidth: 28, - maxHeight: 28, - ), - ), - ], - ), - ], - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - spacing: 4, - children: [ - Text( - kPresenceActivityTypes[activity.type], - ).tr(), - Icon( - kPresenceActivityIcons[activity.type], - size: 16, - fill: 1, - ), - ], - ), - if (activity.manualId == 'spotify' && - activity.meta != null) - StreamBuilder( - stream: Stream.periodic( - const Duration(seconds: 1), - ), - builder: (context, snapshot) { - final now = DateTime.now(); - final meta = - activity.meta - as Map; - final progressMs = - meta['progress_ms'] as int? ?? 0; - final durationMs = - meta['track_duration_ms'] as int? ?? - 1; - final elapsed = - now - .difference(activity.createdAt) - .inMilliseconds; - final currentProgressMs = - (progressMs + elapsed) % durationMs; - final progressValue = - currentProgressMs / durationMs; - if (progressValue != _endProgress) { - _startProgress = _endProgress; - _endProgress = progressValue; - _progressAnimation = Tween( - begin: _startProgress, - end: _endProgress, - ).animate(_progressController); - _progressController.forward( - from: 0.0, - ); - } - return AnimatedBuilder( - animation: _progressAnimation, - builder: (context, child) { - final animatedValue = - _progressAnimation.value; - final animatedProgressMs = - (animatedValue * durationMs) - .toInt(); - final currentMin = - animatedProgressMs ~/ 60000; - final currentSec = - (animatedProgressMs % 60000) ~/ - 1000; - final totalMin = - durationMs ~/ 60000; - final totalSec = - (durationMs % 60000) ~/ 1000; - return Column( - crossAxisAlignment: - CrossAxisAlignment.start, - spacing: 4, - children: [ - LinearProgressIndicator( - value: animatedValue, - backgroundColor: - Colors.grey.shade300, - trackGap: 0, - stopIndicatorColor: - Colors.green, - valueColor: - AlwaysStoppedAnimation< - Color - >(Colors.green), - ).padding(top: 3), - Text( - '${currentMin.toString().padLeft(2, '0')}:${currentSec.toString().padLeft(2, '0')} / ${totalMin.toString().padLeft(2, '0')}:${totalSec.toString().padLeft(2, '0')}', - style: TextStyle( - fontSize: 12, - color: Colors.green, - ), - ), - ], - ); - }, - ); - }, - ) - else - StreamBuilder( - stream: Stream.periodic( - const Duration(seconds: 1), - ), - builder: (context, snapshot) { - final now = DateTime.now(); - - final duration = now.difference( - activity.createdAt, - ); - final hours = duration.inHours - .toString() - .padLeft(2, '0'); - final minutes = (duration.inMinutes % - 60) - .toString() - .padLeft(2, '0'); - final seconds = (duration.inSeconds % - 60) - .toString() - .padLeft(2, '0'); - return Text( - '$hours:$minutes:$seconds', - ).textColor(Colors.green); - }, - ), - if (activity.subtitle?.isNotEmpty ?? false) - Row( - spacing: 2, - children: [ - Flexible( - child: Text(activity.subtitle!), - ), - if (activity.titleUrl != null && - activity.titleUrl!.isNotEmpty) - IconButton( - visualDensity: const VisualDensity( - vertical: -4, - ), - onPressed: () { - launchUrlString( - activity.titleUrl!, - ); - }, - icon: const Icon( - Symbols.launch_rounded, - ), - iconSize: 16, - padding: EdgeInsets.all(4), - constraints: const BoxConstraints( - maxWidth: 28, - maxHeight: 28, - ), - ), - ], - ), - if (activity.caption?.isNotEmpty ?? false) - Text(activity.caption!), - ], - ), - ), - ).padding(horizontal: 8), - if (activity.manualId == 'spotify') - Positioned( - top: 16, - right: 24, - child: Tooltip( - message: 'Listening on Spotify', - child: Image.asset( - 'assets/images/oidc/spotify.png', - width: 24, - height: 24, - color: - Theme.of(context).colorScheme.onSurface, - ), - ), - ), - ], - ); - }), - ], - ).padding(horizontal: 8, top: 8, bottom: 16), - ), - loading: () => const Center(child: CircularProgressIndicator()), - error: - (error, stack) => - Center(child: Text('Error loading activities: $error')), - ); - }, - ); - } -}