satoyos/Shuffle100

View on GitHub
app/lib/table_screen_patches/table_patch.rb

Summary

Maintainability
C
1 day
Test Coverage
module ProMotion
  module Table
    include ProMotion::Styling
    include ProMotion::Table::Searchable
    include ProMotion::Table::Refreshable
    include ProMotion::Table::Indexable
    include ProMotion::Table::Longpressable
    include ProMotion::Table::Utils
    include ProMotion::TableBuilder

    attr_reader :promotion_table_data

    def table_view
      self.view
    end

    def screen_setup
      check_table_data
      set_up_header_footer_views
      set_up_searchable
      set_up_refreshable
      set_up_longpressable
      set_up_row_height
    end

    def on_live_reload
      update_table_data
    end

    def check_table_data
      mp("Missing #table_data method in TableScreen #{self.class.to_s}.", force_color: :red) unless self.respond_to?(:table_data)
    end

    def promotion_table_data
      @promotion_table_data ||= TableData.new(table_data, table_view, setup_search_method)
    end

    def set_up_header_footer_views
      [:header, :footer].each do |hf_view|
        if self.respond_to?("table_#{hf_view}_view".to_sym)
          view = self.send("table_#{hf_view}_view")
          if view.is_a? UIView
            self.tableView.send(camelize("set_table_#{hf_view}_view:"), view)
          else
            mp "Table #{hf_view} view must be a UIView.", force_color: :yellow
          end
        end
      end
    end

    def set_up_searchable
      if self.class.respond_to?(:get_searchable) && self.class.get_searchable
        self.make_searchable(self.class.get_searchable_params)
      end
    end

    def setup_search_method
      params = self.class.get_searchable_params
      if params.nil?
        return nil
      else
        @search_method || begin
          params = self.class.get_searchable_params
          @search_action = params[:with] || params[:find_by] || params[:search_by] || params[:filter_by]
          @search_action = method(@search_action) if @search_action.is_a?(Symbol) || @search_action.is_a?(String)
          @search_action
        end
      end
    end

    def set_up_refreshable
      if self.class.respond_to?(:get_refreshable) && self.class.get_refreshable
        if defined?(UIRefreshControl)
          self.make_refreshable(self.class.get_refreshable_params)
        else
          mp "To use the refresh control on < iOS 6, you need to include the CocoaPod 'CKRefreshControl'.", force_color: :yellow
        end
      end
    end

    def set_up_longpressable
      if self.class.respond_to?(:get_longpressable) && self.class.get_longpressable
        self.make_longpressable(self.class.get_longpressable_params)
      end
    end

    def set_up_row_height
      if self.class.respond_to?(:get_row_height) && params = self.class.get_row_height
        self.view.rowHeight = params[:height]
        self.view.estimatedRowHeight = params[:estimated]
      end
    end

    def searching?
      self.promotion_table_data.filtered?
    end

    def search_string
      self.promotion_table_data.search_string
    end

    def update_table_view_data(data, args = {})
      self.promotion_table_data.data = data
      if args[:index_paths]
        args[:animation] ||= UITableViewRowAnimationNone
        table_view.beginUpdates
        table_view.reloadRowsAtIndexPaths(Array(args[:index_paths]), withRowAnimation: args[:animation])
        table_view.endUpdates
      else
        table_view.reloadData
      end
    end

    def accessory_toggled_switch(switch)
      table_cell = closest_parent(UITableViewCell, switch)
      index_path = closest_parent(UITableView, table_cell).indexPathForCell(table_cell)

      if index_path
        data_cell = cell_at(index_path: index_path)
        data_cell[:accessory][:arguments][:value] = switch.isOn if data_cell[:accessory][:arguments].is_a?(Hash)
        trigger_action(data_cell[:accessory][:action], data_cell[:accessory][:arguments], index_path) if data_cell[:accessory][:action]
      end
    end

    def delete_row(index_paths, animation = nil)
      deletable_index_paths = []
      index_paths = [index_paths] if index_paths.kind_of?(NSIndexPath)
      index_paths.each do |index_path|
        delete_cell = false

        delete_cell = trigger_action(:on_cell_deleted, cell_at(index_path: index_path), index_path) if respond_to?(:on_cell_deleted)
        unless delete_cell == false
          self.promotion_table_data.delete_cell(index_path: index_path)
          deletable_index_paths << index_path
        end
      end
      table_view.deleteRowsAtIndexPaths(deletable_index_paths, withRowAnimation: map_row_animation_symbol(animation)) if deletable_index_paths.length > 0
    end

    def update_table_data(args = {})
      args = { index_paths: args } unless args.is_a?(Hash)

      self.update_table_view_data(self.table_data, args)
    end

    def toggle_edit_mode(animated = true)
      edit_mode({enabled: !editing?, animated: animated})
    end

    def edit_mode(args = {})
      args[:enabled] = false if args[:enabled].nil?
      args[:animated] = true if args[:animated].nil?

      setEditing(args[:enabled], animated:args[:animated])
    end

    def edit_mode?
      !!isEditing
    end

    # Returns the data cell
    def cell_at(args = {})
      self.promotion_table_data.cell(args)
    end

    ########## Cocoa touch methods #################
    def numberOfSectionsInTableView(_)
      self.promotion_table_data.sections.length
    end

    # Number of cells
    def tableView(_, numberOfRowsInSection: section)
      self.promotion_table_data.section_length(section)
    end

    def tableView(_, titleForHeaderInSection: section)
      section = promotion_table_data.section(section)
      section && section[:title]
    end

    def tableView(_, titleForFooterInSection: section)
      section = promotion_table_data.section(section)
      section && section[:footer]
    end

    # Set table_data_index if you want the right hand index column (jumplist)
    def sectionIndexTitlesForTableView(_)
      return if searching?
      return self.table_data_index if self.respond_to?(:table_data_index)
      nil
    end

    def tableView(_, cellForRowAtIndexPath: index_path)
      params = index_path_to_section_index(index_path: index_path)
      data_cell = cell_at(index: params[:index], section: params[:section])
      return UITableViewCell.alloc.init unless data_cell
      create_table_cell(data_cell)
    end

    def tableView(_, willDisplayCell: table_cell, forRowAtIndexPath: index_path)
      data_cell = cell_at(index_path: index_path)
      try :will_display_cell, table_cell, index_path
      table_cell.send(:will_display) if table_cell.respond_to?(:will_display)
      table_cell.send(:restyle!) if table_cell.respond_to?(:restyle!) # Teacup compatibility
    end

    def tableView(_, heightForRowAtIndexPath: index_path)
      (cell_at(index_path: index_path)[:height] || tableView.rowHeight).to_f
    end

    def tableView(table_view, didSelectRowAtIndexPath: index_path)
=begin
      ###
      puts "++ Current table_view => #{table_view}" if BW2.debug?
      ###
=end
      data_cell = cell_at(index_path: index_path)
      table_view.deselectRowAtIndexPath(index_path, animated: true) unless data_cell[:keep_selection] == true
      trigger_action(data_cell[:action], data_cell[:arguments], index_path) if data_cell[:action]
    end

    def tableView(_, canEditRowAtIndexPath:index_path)
      data_cell = cell_at(index_path: index_path, unfiltered: !searching?)
      [:insert,:delete].include?(data_cell[:editing_style])
    end

    def tableView(_, editingStyleForRowAtIndexPath: index_path)
      data_cell = cell_at(index_path: index_path, unfiltered: !searching?)
      map_cell_editing_style(data_cell[:editing_style])
    end

    def tableView(_, commitEditingStyle: editing_style, forRowAtIndexPath: index_path)
      if editing_style == UITableViewCellEditingStyleDelete
        delete_row(index_path)
      end
    end

    def tableView(_, canMoveRowAtIndexPath:index_path)
      data_cell = cell_at(index_path: index_path, unfiltered: !searching?)

      if (!data_cell[:moveable].nil? || data_cell[:moveable].is_a?(Symbol)) && data_cell[:moveable] != false
        true
      else
        false
      end
    end

    def tableView(_, targetIndexPathForMoveFromRowAtIndexPath:source_index_path, toProposedIndexPath:proposed_destination_index_path)
      data_cell = cell_at(index_path: source_index_path, unfiltered: !searching?)

      if data_cell[:moveable] == :section && source_index_path.section != proposed_destination_index_path.section
        source_index_path
      else
        proposed_destination_index_path
      end
    end

    def tableView(_, moveRowAtIndexPath:from_index_path, toIndexPath:to_index_path)
      self.promotion_table_data.move_cell(from_index_path, to_index_path)

      if self.respond_to?("on_cell_moved:")
        args = {
            paths: {
                from: from_index_path,
                to: to_index_path
            },
            cell: self.promotion_table_data.section(to_index_path.section)[:cells][to_index_path.row]
        }
        send(:on_cell_moved, args)
      else
        mp "Implement the on_cell_moved method in your PM::TableScreen to be notified when a user moves a cell.", force_color: :yellow
      end
    end

    def tableView(table_view, sectionForSectionIndexTitle: title, atIndex: index)
      return index unless ["{search}", UITableViewIndexSearch].include?(self.table_data_index[0])

      if index == 0
        table_view.scrollRectToVisible(CGRectMake(0.0, 0.0, 1.0, 1.0), animated: false)
        -1 # NOTE: returning -1 since NSNotFound has been removed in Mojave/Marzipan
      else
        index - 1
      end
    end

    def deleteRowsAtIndexPaths(index_paths, withRowAnimation: animation)
      mp "ProMotion expects you to use 'delete_cell(index_paths, animation)'' instead of 'deleteRowsAtIndexPaths(index_paths, withRowAnimation:animation)'.", force_color: :yellow
      delete_row(index_paths, animation)
    end

    # Section header view methods
    def tableView(_, viewForHeaderInSection: index)
      section = promotion_table_data.section(index)
      view = section[:title_view]
      view = section[:title_view].new if section[:title_view].respond_to?(:new)
      view.on_load if view.respond_to?(:on_load)
      view.title = section[:title] if view.respond_to?(:title=)
      view
    end

    def tableView(_, heightForHeaderInSection: index)
      section = promotion_table_data.section(index)
      if section[:title_view] || section[:title].to_s.length > 0
        if section[:title_view_height]
          section[:title_view_height]
        elsif (section_header = tableView(_, viewForHeaderInSection: index)) && section_header.respond_to?(:height)
          section_header.height
        else
          tableView.sectionHeaderHeight
        end
      else
        0.0
      end
    end

    def tableView(_, willDisplayHeaderView:view, forSection:section)
      action = :will_display_header
      if respond_to?(action)
        case self.method(action).arity
        when 0 then self.send(action)
        when 2 then self.send(action, view, section)
        else self.send(action, view)
        end
      end
    end

    # Section footer view methods
    def tableView(_, viewForFooterInSection: index)
      section = promotion_table_data.section(index)
      view = section[:footer_view]
      view = section[:footer_view].new if section[:footer_view].respond_to?(:new)
      view.on_load if view.respond_to?(:on_load)
      view.title = section[:footer] if view.respond_to?(:title=)
      view
    end

    def tableView(_, heightForFooterInSection: index)
      section = promotion_table_data.section(index)
      if section[:footer_view] || section[:footer].to_s.length > 0
        if section[:footer_view_height]
          section[:footer_view_height]
        elsif (section_footer = tableView(_, viewForFooterInSection: index)) && section_footer.respond_to?(:height)
          section_footer.height
        else
          tableView.sectionFooterHeight
        end
      else
        0.0
      end
    end

    def tableView(_, willDisplayFooterView:view, forSection:section)
      action = :will_display_footer
      if respond_to?(action)
        case self.method(action).arity
        when 0 then self.send(action)
        when 2 then self.send(action, view, section)
        else self.send(action, view)
        end
      end
    end

    protected

    def map_cell_editing_style(symbol)
      {
          none:   UITableViewCellEditingStyleNone,
          delete: UITableViewCellEditingStyleDelete,
          insert: UITableViewCellEditingStyleInsert
      }[symbol] || symbol || UITableViewCellEditingStyleNone
    end

    def map_row_animation_symbol(symbol)
      symbol ||= UITableViewRowAnimationAutomatic
      {
          fade:       UITableViewRowAnimationFade,
          right:      UITableViewRowAnimationRight,
          left:       UITableViewRowAnimationLeft,
          top:        UITableViewRowAnimationTop,
          bottom:     UITableViewRowAnimationBottom,
          none:       UITableViewRowAnimationNone,
          middle:     UITableViewRowAnimationMiddle,
          automatic:  UITableViewRowAnimationAutomatic
      }[symbol] || symbol || UITableViewRowAnimationAutomatic
    end

    def self.included(base)
      base.extend(TableClassMethods)
    end
  end
end